/*
 *    Text layout/format tests
 *
 * Copyright 2012, 2014-2017 Nikolay Sivov for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS

#include <assert.h>
#include <math.h>
#include <limits.h>

#include "windows.h"
#include "dwrite_3.h"

#include "wine/test.h"

static const WCHAR tahomaW[] = {'T','a','h','o','m','a',0};
static const WCHAR enusW[] = {'e','n','-','u','s',0};

struct testanalysissink
{
    IDWriteTextAnalysisSink IDWriteTextAnalysisSink_iface;
    DWRITE_SCRIPT_ANALYSIS sa; /* last analysis, with SetScriptAnalysis() */
};

static inline struct testanalysissink *impl_from_IDWriteTextAnalysisSink(IDWriteTextAnalysisSink *iface)
{
    return CONTAINING_RECORD(iface, struct testanalysissink, IDWriteTextAnalysisSink_iface);
}

/* test IDWriteTextAnalysisSink */
static HRESULT WINAPI analysissink_QueryInterface(IDWriteTextAnalysisSink *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSink) || IsEqualIID(riid, &IID_IUnknown))
    {
        *obj = iface;
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI analysissink_AddRef(IDWriteTextAnalysisSink *iface)
{
    return 2;
}

static ULONG WINAPI analysissink_Release(IDWriteTextAnalysisSink *iface)
{
    return 1;
}

static HRESULT WINAPI analysissink_SetScriptAnalysis(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, DWRITE_SCRIPT_ANALYSIS const* sa)
{
    struct testanalysissink *sink = impl_from_IDWriteTextAnalysisSink(iface);
    sink->sa = *sa;
    return S_OK;
}

static HRESULT WINAPI analysissink_SetLineBreakpoints(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, DWRITE_LINE_BREAKPOINT const* breakpoints)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI analysissink_SetBidiLevel(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, UINT8 explicitLevel, UINT8 resolvedLevel)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI analysissink_SetNumberSubstitution(IDWriteTextAnalysisSink *iface,
    UINT32 position, UINT32 length, IDWriteNumberSubstitution* substitution)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static IDWriteTextAnalysisSinkVtbl analysissinkvtbl = {
    analysissink_QueryInterface,
    analysissink_AddRef,
    analysissink_Release,
    analysissink_SetScriptAnalysis,
    analysissink_SetLineBreakpoints,
    analysissink_SetBidiLevel,
    analysissink_SetNumberSubstitution
};

static struct testanalysissink analysissink = {
    { &analysissinkvtbl },
    { 0 }
};

/* test IDWriteTextAnalysisSource */
static HRESULT WINAPI analysissource_QueryInterface(IDWriteTextAnalysisSource *iface,
    REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSource) || IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IDWriteTextAnalysisSource_AddRef(iface);
        return S_OK;
    }
    return E_NOINTERFACE;
}

static ULONG WINAPI analysissource_AddRef(IDWriteTextAnalysisSource *iface)
{
    return 2;
}

static ULONG WINAPI analysissource_Release(IDWriteTextAnalysisSource *iface)
{
    return 1;
}

static const WCHAR *g_source;

static HRESULT WINAPI analysissource_GetTextAtPosition(IDWriteTextAnalysisSource *iface,
    UINT32 position, WCHAR const** text, UINT32* text_len)
{
    if (position >= lstrlenW(g_source))
    {
        *text = NULL;
        *text_len = 0;
    }
    else
    {
        *text = &g_source[position];
        *text_len = lstrlenW(g_source) - position;
    }

    return S_OK;
}

static HRESULT WINAPI analysissource_GetTextBeforePosition(IDWriteTextAnalysisSource *iface,
    UINT32 position, WCHAR const** text, UINT32* text_len)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static DWRITE_READING_DIRECTION WINAPI analysissource_GetParagraphReadingDirection(
    IDWriteTextAnalysisSource *iface)
{
    ok(0, "unexpected\n");
    return DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
}

static HRESULT WINAPI analysissource_GetLocaleName(IDWriteTextAnalysisSource *iface,
    UINT32 position, UINT32* text_len, WCHAR const** locale)
{
    *locale = NULL;
    *text_len = 0;
    return S_OK;
}

static HRESULT WINAPI analysissource_GetNumberSubstitution(IDWriteTextAnalysisSource *iface,
    UINT32 position, UINT32* text_len, IDWriteNumberSubstitution **substitution)
{
    ok(0, "unexpected\n");
    return E_NOTIMPL;
}

static IDWriteTextAnalysisSourceVtbl analysissourcevtbl = {
    analysissource_QueryInterface,
    analysissource_AddRef,
    analysissource_Release,
    analysissource_GetTextAtPosition,
    analysissource_GetTextBeforePosition,
    analysissource_GetParagraphReadingDirection,
    analysissource_GetLocaleName,
    analysissource_GetNumberSubstitution
};

static IDWriteTextAnalysisSource analysissource = { &analysissourcevtbl };

static IDWriteFactory *create_factory(void)
{
    IDWriteFactory *factory;
    HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_ISOLATED, &IID_IDWriteFactory, (IUnknown**)&factory);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    return factory;
}

/* obvious limitation is that only last script data is returned, so this
   helper is suitable for single script strings only */
static void get_script_analysis(const WCHAR *str, UINT32 len, DWRITE_SCRIPT_ANALYSIS *sa)
{
    IDWriteTextAnalyzer *analyzer;
    IDWriteFactory *factory;
    HRESULT hr;

    g_source = str;

    factory = create_factory();
    hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextAnalyzer_AnalyzeScript(analyzer, &analysissource, 0, len, &analysissink.IDWriteTextAnalysisSink_iface);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    *sa = analysissink.sa;
    IDWriteFactory_Release(factory);
}

static IDWriteFontFace *get_fontface_from_format(IDWriteTextFormat *format)
{
    IDWriteFontCollection *collection;
    IDWriteFontFamily *family;
    IDWriteFontFace *fontface;
    IDWriteFont *font;
    WCHAR nameW[255];
    UINT32 index;
    BOOL exists;
    HRESULT hr;

    hr = IDWriteTextFormat_GetFontCollection(format, &collection);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_GetFontFamilyName(format, nameW, ARRAY_SIZE(nameW));
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontCollection_FindFamilyName(collection, nameW, &index, &exists);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontCollection_GetFontFamily(collection, index, &family);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteFontCollection_Release(collection);

    hr = IDWriteFontFamily_GetFirstMatchingFont(family,
        IDWriteTextFormat_GetFontWeight(format),
        IDWriteTextFormat_GetFontStretch(format),
        IDWriteTextFormat_GetFontStyle(format),
        &font);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFont_CreateFontFace(font, &fontface);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    IDWriteFont_Release(font);
    IDWriteFontFamily_Release(family);

    return fontface;
}

#define EXPECT_REF(obj,ref) _expect_ref((IUnknown*)obj, ref, __LINE__)
static void _expect_ref(IUnknown* obj, ULONG ref, int line)
{
    ULONG rc;
    IUnknown_AddRef(obj);
    rc = IUnknown_Release(obj);
    ok_(__FILE__,line)(rc == ref, "expected refcount %d, got %d\n", ref, rc);
}

enum drawcall_modifiers_kind {
    DRAW_EFFECT         = 0x1000
};

enum drawcall_kind {
    DRAW_GLYPHRUN      = 0,
    DRAW_UNDERLINE     = 1,
    DRAW_STRIKETHROUGH = 2,
    DRAW_INLINE        = 3,
    DRAW_LAST_KIND     = 4,
    DRAW_TOTAL_KINDS   = 5,
    DRAW_KINDS_MASK    = 0xff
};

static const char *get_draw_kind_name(unsigned short kind)
{
    static const char *kind_names[] = {
      "GLYPH_RUN",
      "UNDERLINE",
      "STRIKETHROUGH",
      "INLINE",
      "END_OF_SEQ",

      "GLYPH_RUN|EFFECT",
      "UNDERLINE|EFFECT",
      "STRIKETHROUGH|EFFECT",
      "INLINE|EFFECT",
      "END_OF_SEQ"
    };
    if ((kind & DRAW_KINDS_MASK) > DRAW_LAST_KIND)
        return "unknown";
    return (kind & DRAW_EFFECT) ? kind_names[(kind & DRAW_KINDS_MASK) + DRAW_TOTAL_KINDS] :
        kind_names[kind];
}

struct drawcall_entry {
    enum drawcall_kind kind;
    WCHAR string[10]; /* only meaningful for DrawGlyphRun() */
    WCHAR locale[LOCALE_NAME_MAX_LENGTH];
    UINT32 glyphcount; /* only meaningful for DrawGlyphRun() */
    UINT32 bidilevel;
};

struct drawcall_sequence
{
    int count;
    int size;
    struct drawcall_entry *sequence;
};

struct drawtestcontext {
    unsigned short kind;
    BOOL todo;
    int *failcount;
    const char *file;
    int line;
};

#define NUM_CALL_SEQUENCES 1
#define RENDERER_ID        0
static struct drawcall_sequence *sequences[NUM_CALL_SEQUENCES];
static struct drawcall_sequence *expected_seq[1];

static void add_call(struct drawcall_sequence **seq, int sequence_index, const struct drawcall_entry *call)
{
    struct drawcall_sequence *call_seq = seq[sequence_index];

    if (!call_seq->sequence) {
        call_seq->size = 10;
        call_seq->sequence = HeapAlloc(GetProcessHeap(), 0, call_seq->size * sizeof (struct drawcall_entry));
    }

    if (call_seq->count == call_seq->size) {
        call_seq->size *= 2;
        call_seq->sequence = HeapReAlloc(GetProcessHeap(), 0,
                                        call_seq->sequence,
                                        call_seq->size * sizeof (struct drawcall_entry));
    }

    assert(call_seq->sequence);
    call_seq->sequence[call_seq->count++] = *call;
}

static inline void flush_sequence(struct drawcall_sequence **seg, int sequence_index)
{
    struct drawcall_sequence *call_seq = seg[sequence_index];

    HeapFree(GetProcessHeap(), 0, call_seq->sequence);
    call_seq->sequence = NULL;
    call_seq->count = call_seq->size = 0;
}

static void init_call_sequences(struct drawcall_sequence **seq, int n)
{
    int i;

    for (i = 0; i < n; i++)
        seq[i] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct drawcall_sequence));
}

static void ok_sequence_(struct drawcall_sequence **seq, int sequence_index,
    const struct drawcall_entry *expected, const char *context, BOOL todo,
    const char *file, int line)
{
    static const struct drawcall_entry end_of_sequence = { DRAW_LAST_KIND };
    struct drawcall_sequence *call_seq = seq[sequence_index];
    const struct drawcall_entry *actual, *sequence;
    int failcount = 0;

    add_call(seq, sequence_index, &end_of_sequence);

    sequence = call_seq->sequence;
    actual = sequence;

    while (expected->kind != DRAW_LAST_KIND && actual->kind != DRAW_LAST_KIND) {
        if (expected->kind != actual->kind) {
            if (todo) {
                failcount++;
                todo_wine
                    ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
                        context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));

                flush_sequence(seq, sequence_index);
                return;
            }
            else
                ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
                    context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));
        }
        else if ((expected->kind & DRAW_KINDS_MASK) == DRAW_GLYPHRUN) {
            int cmp = lstrcmpW(expected->string, actual->string);
            if (cmp != 0 && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: glyphrun string %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->string), wine_dbgstr_w(actual->string));
            }
            else
                ok_(file, line) (cmp == 0, "%s: glyphrun string %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->string), wine_dbgstr_w(actual->string));

            cmp = lstrcmpW(expected->locale, actual->locale);
            if (cmp != 0 && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: glyph run locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));
            }
            else
                ok_(file, line) (cmp == 0, "%s: glyph run locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));

            if (expected->glyphcount != actual->glyphcount && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: wrong glyph count, %u was expected, but got %u instead\n",
                    context, expected->glyphcount, actual->glyphcount);
            }
            else
                ok_(file, line) (expected->glyphcount == actual->glyphcount,
                    "%s: wrong glyph count, %u was expected, but got %u instead\n",
                    context, expected->glyphcount, actual->glyphcount);

            if (expected->bidilevel != actual->bidilevel && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: wrong bidi level, %u was expected, but got %u instead\n",
                    context, expected->bidilevel, actual->bidilevel);
            }
            else
                ok_(file, line) (expected->bidilevel == actual->bidilevel,
                    "%s: wrong bidi level, %u was expected, but got %u instead\n",
                    context, expected->bidilevel, actual->bidilevel);
        }
        else if ((expected->kind & DRAW_KINDS_MASK) == DRAW_UNDERLINE) {
            int cmp = lstrcmpW(expected->locale, actual->locale);
            if (cmp != 0 && todo) {
                failcount++;
            todo_wine
                ok_(file, line) (0, "%s: underline locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));
            }
            else
                ok_(file, line) (cmp == 0, "%s: underline locale %s was expected, but got %s instead\n",
                    context, wine_dbgstr_w(expected->locale), wine_dbgstr_w(actual->locale));
        }
        expected++;
        actual++;
    }

    if (todo) {
        todo_wine {
            if (expected->kind != DRAW_LAST_KIND || actual->kind != DRAW_LAST_KIND) {
                failcount++;
                ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
                    context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));
            }
        }
    }
    else if (expected->kind != DRAW_LAST_KIND || actual->kind != DRAW_LAST_KIND)
        ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
            context, get_draw_kind_name(expected->kind), get_draw_kind_name(actual->kind));

    if (todo && !failcount) /* succeeded yet marked todo */
        todo_wine
            ok_(file, line)(1, "%s: marked \"todo_wine\" but succeeds\n", context);

    flush_sequence(seq, sequence_index);
}

#define ok_sequence(seq, index, exp, contx, todo) \
        ok_sequence_(seq, index, (exp), (contx), (todo), __FILE__, __LINE__)

static HRESULT WINAPI testrenderer_QI(IDWriteTextRenderer *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteTextRenderer) ||
        IsEqualIID(riid, &IID_IDWritePixelSnapping) ||
        IsEqualIID(riid, &IID_IUnknown)
    ) {
        *obj = iface;
        return S_OK;
    }

    *obj = NULL;

    /* IDWriteTextRenderer1 overrides drawing calls, ignore for now */
    if (IsEqualIID(riid, &IID_IDWriteTextRenderer1))
        return E_NOINTERFACE;

    ok(0, "unexpected QI %s\n", wine_dbgstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI testrenderer_AddRef(IDWriteTextRenderer *iface)
{
    return 2;
}

static ULONG WINAPI testrenderer_Release(IDWriteTextRenderer *iface)
{
    return 1;
}

struct renderer_context {
    BOOL gdicompat;
    BOOL use_gdi_natural;
    BOOL snapping_disabled;
    DWRITE_MATRIX m;
    FLOAT ppdip;
    FLOAT originX;
    FLOAT originY;
    IDWriteTextFormat *format;
    const WCHAR *familyW;
};

static HRESULT WINAPI testrenderer_IsPixelSnappingDisabled(IDWriteTextRenderer *iface,
    void *context, BOOL *disabled)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    if (ctxt)
        *disabled = ctxt->snapping_disabled;
    else
        *disabled = TRUE;
    return S_OK;
}

static HRESULT WINAPI testrenderer_GetCurrentTransform(IDWriteTextRenderer *iface,
    void *context, DWRITE_MATRIX *m)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    ok(!ctxt->snapping_disabled, "expected enabled snapping\n");
    *m = ctxt->m;
    return S_OK;
}

static HRESULT WINAPI testrenderer_GetPixelsPerDip(IDWriteTextRenderer *iface,
    void *context, FLOAT *pixels_per_dip)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    *pixels_per_dip = ctxt->ppdip;
    return S_OK;
}

#define TEST_MEASURING_MODE(ctxt, mode) test_measuring_mode(ctxt, mode, __LINE__)
static void test_measuring_mode(const struct renderer_context *ctxt, DWRITE_MEASURING_MODE mode, int line)
{
    if (ctxt->gdicompat) {
        if (ctxt->use_gdi_natural)
            ok_(__FILE__, line)(mode == DWRITE_MEASURING_MODE_GDI_NATURAL, "got %d\n", mode);
        else
            ok_(__FILE__, line)(mode == DWRITE_MEASURING_MODE_GDI_CLASSIC, "got %d\n", mode);
    }
    else
        ok_(__FILE__, line)(mode == DWRITE_MEASURING_MODE_NATURAL, "got %d\n", mode);
}

static HRESULT WINAPI testrenderer_DrawGlyphRun(IDWriteTextRenderer *iface,
    void *context,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_MEASURING_MODE mode,
    DWRITE_GLYPH_RUN const *run,
    DWRITE_GLYPH_RUN_DESCRIPTION const *descr,
    IUnknown *effect)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    struct drawcall_entry entry;
    DWRITE_SCRIPT_ANALYSIS sa;

    if (ctxt) {
        TEST_MEASURING_MODE(ctxt, mode);
        ctxt->originX = baselineOriginX;
        ctxt->originY = baselineOriginY;
    }

    ok(descr->stringLength < ARRAY_SIZE(entry.string), "string is too long\n");
    if (descr->stringLength && descr->stringLength < ARRAY_SIZE(entry.string)) {
        memcpy(entry.string, descr->string, descr->stringLength*sizeof(WCHAR));
        entry.string[descr->stringLength] = 0;
    }
    else
        entry.string[0] = 0;

    /* see what's reported for control codes runs */
    get_script_analysis(descr->string, descr->stringLength, &sa);
    if (sa.shapes == DWRITE_SCRIPT_SHAPES_NO_VISUAL) {
        UINT32 i;

        /* glyphs are not reported at all for control code runs */
        ok(run->glyphCount == 0, "got %u\n", run->glyphCount);
        ok(run->glyphAdvances != NULL, "advances array %p\n", run->glyphAdvances);
        ok(run->glyphOffsets != NULL, "offsets array %p\n", run->glyphOffsets);
        ok(run->fontFace != NULL, "got %p\n", run->fontFace);
        /* text positions are still valid */
        ok(descr->string != NULL, "got string %p\n", descr->string);
        ok(descr->stringLength > 0, "got string length %u\n", descr->stringLength);
        ok(descr->clusterMap != NULL, "clustermap %p\n", descr->clusterMap);
        for (i = 0; i < descr->stringLength; i++)
            ok(descr->clusterMap[i] == i, "got %u\n", descr->clusterMap[i]);
    }

    entry.kind = DRAW_GLYPHRUN;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    ok(lstrlenW(descr->localeName) < LOCALE_NAME_MAX_LENGTH, "unexpectedly long locale name\n");
    lstrcpyW(entry.locale, descr->localeName);
    entry.glyphcount = run->glyphCount;
    entry.bidilevel = run->bidiLevel;
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static HRESULT WINAPI testrenderer_DrawUnderline(IDWriteTextRenderer *iface,
    void *context,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_UNDERLINE const* underline,
    IUnknown *effect)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    struct drawcall_entry entry = { 0 };

    if (ctxt)
        TEST_MEASURING_MODE(ctxt, underline->measuringMode);

    ok(underline->runHeight > 0.0f, "Expected non-zero run height\n");
    if (ctxt && ctxt->format) {
        DWRITE_FONT_METRICS metrics;
        IDWriteFontFace *fontface;
        FLOAT emsize;

        fontface = get_fontface_from_format(ctxt->format);
        emsize = IDWriteTextFormat_GetFontSize(ctxt->format);
        IDWriteFontFace_GetMetrics(fontface, &metrics);

        ok(emsize == metrics.designUnitsPerEm, "Unexpected font size %f\n", emsize);
        /* Expected height is in design units, allow some absolute difference from it. Seems to only happen on Vista */
        ok(fabs(metrics.capHeight - underline->runHeight) < 2.0f, "Expected runHeight %u, got %f, family %s\n",
                metrics.capHeight, underline->runHeight, wine_dbgstr_w(ctxt->familyW));

        IDWriteFontFace_Release(fontface);
    }

    entry.kind = DRAW_UNDERLINE;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    lstrcpyW(entry.locale, underline->localeName);
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static HRESULT WINAPI testrenderer_DrawStrikethrough(IDWriteTextRenderer *iface,
    void *context,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_STRIKETHROUGH const* strikethrough,
    IUnknown *effect)
{
    struct renderer_context *ctxt = (struct renderer_context*)context;
    struct drawcall_entry entry = { 0 };

    if (ctxt)
        TEST_MEASURING_MODE(ctxt, strikethrough->measuringMode);

    entry.kind = DRAW_STRIKETHROUGH;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static HRESULT WINAPI testrenderer_DrawInlineObject(IDWriteTextRenderer *iface,
    void *context,
    FLOAT originX,
    FLOAT originY,
    IDWriteInlineObject *object,
    BOOL is_sideways,
    BOOL is_rtl,
    IUnknown *effect)
{
    struct drawcall_entry entry = { 0 };
    entry.kind = DRAW_INLINE;
    if (effect)
        entry.kind |= DRAW_EFFECT;
    add_call(sequences, RENDERER_ID, &entry);
    return S_OK;
}

static const IDWriteTextRendererVtbl testrenderervtbl = {
    testrenderer_QI,
    testrenderer_AddRef,
    testrenderer_Release,
    testrenderer_IsPixelSnappingDisabled,
    testrenderer_GetCurrentTransform,
    testrenderer_GetPixelsPerDip,
    testrenderer_DrawGlyphRun,
    testrenderer_DrawUnderline,
    testrenderer_DrawStrikethrough,
    testrenderer_DrawInlineObject
};

static IDWriteTextRenderer testrenderer = { &testrenderervtbl };

/* test IDWriteInlineObject */
static HRESULT WINAPI testinlineobj_QI(IDWriteInlineObject *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteInlineObject) || IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IDWriteInlineObject_AddRef(iface);
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI testinlineobj_AddRef(IDWriteInlineObject *iface)
{
    return 2;
}

static ULONG WINAPI testinlineobj_Release(IDWriteInlineObject *iface)
{
    return 1;
}

static HRESULT WINAPI testinlineobj_Draw(IDWriteInlineObject *iface,
    void* client_drawingontext, IDWriteTextRenderer* renderer,
    FLOAT originX, FLOAT originY, BOOL is_sideways, BOOL is_rtl, IUnknown *drawing_effect)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI testinlineobj_GetMetrics(IDWriteInlineObject *iface, DWRITE_INLINE_OBJECT_METRICS *metrics)
{
    metrics->width = 123.0;
    return 0x8faecafe;
}

static HRESULT WINAPI testinlineobj_GetOverhangMetrics(IDWriteInlineObject *iface, DWRITE_OVERHANG_METRICS *overhangs)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI testinlineobj_GetBreakConditions(IDWriteInlineObject *iface, DWRITE_BREAK_CONDITION *before,
    DWRITE_BREAK_CONDITION *after)
{
    *before = *after = DWRITE_BREAK_CONDITION_MUST_BREAK;
    return 0x8feacafe;
}

static HRESULT WINAPI testinlineobj2_GetBreakConditions(IDWriteInlineObject *iface, DWRITE_BREAK_CONDITION *before,
    DWRITE_BREAK_CONDITION *after)
{
    *before = *after = DWRITE_BREAK_CONDITION_MAY_NOT_BREAK;
    return S_OK;
}

static IDWriteInlineObjectVtbl testinlineobjvtbl = {
    testinlineobj_QI,
    testinlineobj_AddRef,
    testinlineobj_Release,
    testinlineobj_Draw,
    testinlineobj_GetMetrics,
    testinlineobj_GetOverhangMetrics,
    testinlineobj_GetBreakConditions
};

static IDWriteInlineObjectVtbl testinlineobjvtbl2 = {
    testinlineobj_QI,
    testinlineobj_AddRef,
    testinlineobj_Release,
    testinlineobj_Draw,
    testinlineobj_GetMetrics,
    testinlineobj_GetOverhangMetrics,
    testinlineobj2_GetBreakConditions
};

static IDWriteInlineObject testinlineobj = { &testinlineobjvtbl };
static IDWriteInlineObject testinlineobj2 = { &testinlineobjvtbl };
static IDWriteInlineObject testinlineobj3 = { &testinlineobjvtbl2 };

struct test_inline_obj
{
    IDWriteInlineObject IDWriteInlineObject_iface;
    DWRITE_INLINE_OBJECT_METRICS metrics;
    DWRITE_OVERHANG_METRICS overhangs;
};

static inline struct test_inline_obj *impl_from_IDWriteInlineObject(IDWriteInlineObject *iface)
{
    return CONTAINING_RECORD(iface, struct test_inline_obj, IDWriteInlineObject_iface);
}

static HRESULT WINAPI testinlineobj3_GetMetrics(IDWriteInlineObject *iface, DWRITE_INLINE_OBJECT_METRICS *metrics)
{
    struct test_inline_obj *obj = impl_from_IDWriteInlineObject(iface);
    *metrics = obj->metrics;
    return S_OK;
}

static HRESULT WINAPI testinlineobj3_GetOverhangMetrics(IDWriteInlineObject *iface, DWRITE_OVERHANG_METRICS *overhangs)
{
    struct test_inline_obj *obj = impl_from_IDWriteInlineObject(iface);
    *overhangs = obj->overhangs;
    /* Return value is ignored. */
    return E_NOTIMPL;
}

static const IDWriteInlineObjectVtbl testinlineobjvtbl3 = {
    testinlineobj_QI,
    testinlineobj_AddRef,
    testinlineobj_Release,
    testinlineobj_Draw,
    testinlineobj3_GetMetrics,
    testinlineobj3_GetOverhangMetrics,
    testinlineobj_GetBreakConditions,
};

static void test_inline_obj_init(struct test_inline_obj *obj, const DWRITE_INLINE_OBJECT_METRICS *metrics,
        const DWRITE_OVERHANG_METRICS *overhangs)
{
    obj->IDWriteInlineObject_iface.lpVtbl = &testinlineobjvtbl3;
    obj->metrics = *metrics;
    obj->overhangs = *overhangs;
}

struct test_effect
{
    IUnknown IUnknown_iface;
    LONG ref;
};

static inline struct test_effect *test_effect_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, struct test_effect, IUnknown_iface);
}

static HRESULT WINAPI testeffect_QI(IUnknown *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IUnknown_AddRef(iface);
        return S_OK;
    }

    ok(0, "Unexpected riid %s.\n", wine_dbgstr_guid(riid));
    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI testeffect_AddRef(IUnknown *iface)
{
    struct test_effect *effect = test_effect_from_IUnknown(iface);
    return InterlockedIncrement(&effect->ref);
}

static ULONG WINAPI testeffect_Release(IUnknown *iface)
{
    struct test_effect *effect = test_effect_from_IUnknown(iface);
    LONG ref = InterlockedDecrement(&effect->ref);

    if (!ref)
        HeapFree(GetProcessHeap(), 0, effect);

    return ref;
}

static const IUnknownVtbl testeffectvtbl = {
    testeffect_QI,
    testeffect_AddRef,
    testeffect_Release
};

static IUnknown *create_test_effect(void)
{
    struct test_effect *effect;

    effect = HeapAlloc(GetProcessHeap(), 0, sizeof(*effect));
    effect->IUnknown_iface.lpVtbl = &testeffectvtbl;
    effect->ref = 1;

    return &effect->IUnknown_iface;
}

static void test_CreateTextLayout(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    IDWriteTextLayout2 *layout2;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, NULL, 0, NULL, 0.0, 0.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 0.0, 0.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 1.0, 0.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 0.0, 1.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, NULL, 1000.0, 1000.0, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateTextLayout(factory, NULL, 0, format, 100.0f, 100.0f, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 0.0f, 0.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    EXPECT_REF(format, 1);
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    if (hr == S_OK) {
        IDWriteTextLayout1 *layout1;
        IDWriteTextFormat1 *format1;
        IDWriteTextFormat *format;

        hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextLayout1, (void**)&layout1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout1_Release(layout1);

        EXPECT_REF(layout2, 2);
        hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat1, (void**)&format1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        EXPECT_REF(layout2, 3);

        hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat, (void**)&format);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(format == (IDWriteTextFormat*)format1, "got %p, %p\n", format, format1);
        ok(format != (IDWriteTextFormat*)layout2, "got %p, %p\n", format, layout2);
        EXPECT_REF(layout2, 4);

        hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextLayout1, (void**)&layout1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout1_Release(layout1);

        IDWriteTextFormat1_Release(format1);
        IDWriteTextFormat_Release(format);

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat1, (void**)&format1);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        EXPECT_REF(layout2, 3);

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&format);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(format == (IDWriteTextFormat*)format1, "got %p, %p\n", format, format1);
        EXPECT_REF(layout2, 4);

        IDWriteTextFormat1_Release(format1);
        IDWriteTextFormat_Release(format);
        IDWriteTextLayout2_Release(layout2);
    }
    else
        win_skip("IDWriteTextLayout2 is not supported.\n");

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static DWRITE_MATRIX layoutcreate_transforms[] = {
    { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 },
    { 1.0, 0.0, 0.0, 1.0, 0.3, 0.2 },
    { 1.0, 0.0, 0.0, 1.0,-0.3,-0.2 },

    { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
    { 1.0, 2.0, 0.5, 1.0, 0.0, 0.0 },
};

static void test_CreateGdiCompatibleTextLayout(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    FLOAT dimension;
    HRESULT hr;
    int i;

    factory = create_factory();

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, NULL, 0, NULL, 0.0, 0.0, 0.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 0.0, 0.0, 0.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 1.0, 0.0, 0.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 1.0, 0.0, 1.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, NULL, 1000.0, 1000.0, 1.0, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    /* create with text format */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);

    layout = (void*)0xdeadbeef;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, NULL, 0, format, 100.0f, 100.0f, 1.0f, NULL, FALSE, &layout);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(layout == NULL, "got %p\n", layout);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);
    EXPECT_REF(layout, 1);

    IDWriteTextLayout_AddRef(layout);
    EXPECT_REF(format, 1);
    EXPECT_REF(layout, 2);
    IDWriteTextLayout_Release(layout);
    IDWriteTextLayout_Release(layout);

    /* zero length string is okay */
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 0, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    dimension = IDWriteTextLayout_GetMaxWidth(layout);
    ok(dimension == 100.0, "got %f\n", dimension);

    dimension = IDWriteTextLayout_GetMaxHeight(layout);
    ok(dimension == 100.0, "got %f\n", dimension);

    IDWriteTextLayout_Release(layout);

    /* negative, zero ppdip */
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 100.0, 100.0, -1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 100.0, 100.0, 0.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    /* transforms */
    for (i = 0; i < ARRAY_SIZE(layoutcreate_transforms); i++) {
        hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 100.0, 100.0, 1.0,
            &layoutcreate_transforms[i], FALSE, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout_Release(layout);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_CreateTextFormat(void)
{
    static const WCHAR emptyW[] = {0};
    IDWriteFontCollection *collection, *syscoll;
    DWRITE_PARAGRAPH_ALIGNMENT paralign;
    DWRITE_READING_DIRECTION readdir;
    DWRITE_WORD_WRAPPING wrapping;
    DWRITE_TEXT_ALIGNMENT align;
    DWRITE_FLOW_DIRECTION flow;
    DWRITE_LINE_SPACING_METHOD method;
    DWRITE_TRIMMING trimming;
    IDWriteTextFormat *format;
    FLOAT spacing, baseline;
    IDWriteInlineObject *trimmingsign;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    /* zero/negative font size */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 0.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, -10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* invalid font properties */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, 1000, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_ITALIC + 1,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_ITALIC,
        10, 10.0f, enusW, &format);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* empty family name */
    hr = IDWriteFactory_CreateTextFormat(factory, emptyW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    if (0) /* crashes on native */
        hr = IDWriteTextFormat_GetFontCollection(format, NULL);

    collection = NULL;
    hr = IDWriteTextFormat_GetFontCollection(format, &collection);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(collection != NULL, "got %p\n", collection);

    hr = IDWriteFactory_GetSystemFontCollection(factory, &syscoll, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(collection == syscoll, "got %p, was %p\n", syscoll, collection);
    IDWriteFontCollection_Release(syscoll);
    IDWriteFontCollection_Release(collection);

    /* default format properties */
    align = IDWriteTextFormat_GetTextAlignment(format);
    ok(align == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", align);

    paralign = IDWriteTextFormat_GetParagraphAlignment(format);
    ok(paralign == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", paralign);

    wrapping = IDWriteTextFormat_GetWordWrapping(format);
    ok(wrapping == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", wrapping);

    readdir = IDWriteTextFormat_GetReadingDirection(format);
    ok(readdir == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", readdir);

    flow = IDWriteTextFormat_GetFlowDirection(format);
    ok(flow == DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM, "got %d\n", flow);

    hr = IDWriteTextFormat_GetLineSpacing(format, &method, &spacing, &baseline);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(spacing == 0.0, "got %f\n", spacing);
    ok(baseline == 0.0, "got %f\n", baseline);
    ok(method == DWRITE_LINE_SPACING_METHOD_DEFAULT, "got %d\n", method);

    trimming.granularity = DWRITE_TRIMMING_GRANULARITY_WORD;
    trimming.delimiter = 10;
    trimming.delimiterCount = 10;
    trimmingsign = (void*)0xdeadbeef;
    hr = IDWriteTextFormat_GetTrimming(format, &trimming, &trimmingsign);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(trimming.granularity == DWRITE_TRIMMING_GRANULARITY_NONE, "got %d\n", trimming.granularity);
    ok(trimming.delimiter == 0, "got %d\n", trimming.delimiter);
    ok(trimming.delimiterCount == 0, "got %d\n", trimming.delimiterCount);
    ok(trimmingsign == NULL, "got %p\n", trimmingsign);

    /* setters */
    hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_LEADING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_JUSTIFIED+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetParagraphAlignment(format, DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetParagraphAlignment(format, DWRITE_PARAGRAPH_ALIGNMENT_CENTER+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_CHARACTER+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetReadingDirection(format, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM);
    ok(hr == S_OK, "got 0x%08x\n", hr);


    hr = IDWriteTextFormat_SetTrimming(format, &trimming, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* invalid granularity */
    trimming.granularity = 10;
    trimming.delimiter = 0;
    trimming.delimiterCount = 0;
    hr = IDWriteTextFormat_SetTrimming(format, &trimming, NULL);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_GetLocaleName(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR ruW[] = {'r','u',0};
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format, *format2;
    IDWriteFactory *factory;
    WCHAR buff[10];
    UINT32 len;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 0, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&format2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    len = IDWriteTextFormat_GetLocaleNameLength(format2);
    ok(len == 2, "got %u\n", len);
    len = IDWriteTextFormat_GetLocaleNameLength(format);
    ok(len == 2, "got %u\n", len);
    hr = IDWriteTextFormat_GetLocaleName(format2, buff, len);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    hr = IDWriteTextFormat_GetLocaleName(format2, buff, len+1);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buff, ruW), "got %s\n", wine_dbgstr_w(buff));
    hr = IDWriteTextFormat_GetLocaleName(format, buff, len);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    hr = IDWriteTextFormat_GetLocaleName(format, buff, len+1);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buff, ruW), "got %s\n", wine_dbgstr_w(buff));

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteTextFormat_Release(format2);
    IDWriteFactory_Release(factory);
}

static const struct drawcall_entry drawellipsis_seq[] = {
    { DRAW_GLYPHRUN, {0x2026, 0}, {'e','n','-','g','b',0}, 1 },
    { DRAW_LAST_KIND }
};

static void test_CreateEllipsisTrimmingSign(void)
{
    static const WCHAR engbW[] = {'e','n','-','G','B',0};
    DWRITE_INLINE_OBJECT_METRICS metrics;
    DWRITE_BREAK_CONDITION before, after;
    struct renderer_context ctxt;
    IDWriteTextFormat *format;
    IDWriteInlineObject *sign;
    IDWriteFactory *factory;
    IUnknown *unk, *effect;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, engbW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(format, 1);
    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(format, 1);

    hr = IDWriteInlineObject_QueryInterface(sign, &IID_IDWriteTextLayout, (void**)&unk);
    ok(hr == E_NOINTERFACE, "got 0x%08x\n", hr);

if (0) {/* crashes on native */
    hr = IDWriteInlineObject_GetBreakConditions(sign, NULL, NULL);
    hr = IDWriteInlineObject_GetMetrics(sign, NULL);
}
    metrics.width = 0.0;
    metrics.height = 123.0;
    metrics.baseline = 123.0;
    metrics.supportsSideways = TRUE;
    hr = IDWriteInlineObject_GetMetrics(sign, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.width > 0.0, "got %.2f\n", metrics.width);
    ok(metrics.height == 0.0, "got %.2f\n", metrics.height);
    ok(metrics.baseline == 0.0, "got %.2f\n", metrics.baseline);
    ok(!metrics.supportsSideways, "got %d\n", metrics.supportsSideways);

    before = after = DWRITE_BREAK_CONDITION_CAN_BREAK;
    hr = IDWriteInlineObject_GetBreakConditions(sign, &before, &after);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(before == DWRITE_BREAK_CONDITION_NEUTRAL, "got %d\n", before);
    ok(after == DWRITE_BREAK_CONDITION_NEUTRAL, "got %d\n", after);

    /* Draw tests */
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteInlineObject_Draw(sign, NULL, &testrenderer, 0.0, 0.0, FALSE, FALSE, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawellipsis_seq, "ellipsis sign draw test", FALSE);

    effect = create_test_effect();

    EXPECT_REF(effect, 1);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteInlineObject_Draw(sign, NULL, &testrenderer, 0.0f, 0.0f, FALSE, FALSE, effect);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawellipsis_seq, "ellipsis sign draw with effect test", FALSE);
    EXPECT_REF(effect, 1);

    IUnknown_Release(effect);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteInlineObject_Draw(sign, NULL, &testrenderer, 0.0f, 0.0f, FALSE, FALSE, (void *)0xdeadbeef);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawellipsis_seq, "ellipsis sign draw with effect test", FALSE);

    memset(&ctxt, 0, sizeof(ctxt));
    hr = IDWriteInlineObject_Draw(sign, &ctxt, &testrenderer, 123.0f, 456.0f, FALSE, FALSE, NULL);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok(ctxt.originX == 123.0f && ctxt.originY == 456.0f, "Unexpected drawing origin\n");

    IDWriteInlineObject_Release(sign);

    /* Centered format */
    hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_CENTER);
    ok(hr == S_OK, "Failed to set text alignment, hr %#x.\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(&ctxt, 0, sizeof(ctxt));
    hr = IDWriteInlineObject_Draw(sign, &ctxt, &testrenderer, 123.0f, 456.0f, FALSE, FALSE, NULL);
    ok(hr == S_OK, "Failed to draw trimming sign, hr %#x.\n", hr);
    ok(ctxt.originX == 123.0f && ctxt.originY == 456.0f, "Unexpected drawing origin\n");

    IDWriteInlineObject_Release(sign);

    /* non-orthogonal flow/reading combination */
    hr = IDWriteTextFormat_SetReadingDirection(format, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK || broken(hr == E_INVALIDARG) /* vista, win7 */, "got 0x%08x\n", hr);
    if (hr == S_OK) {
        hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
        ok(hr == DWRITE_E_FLOWDIRECTIONCONFLICTS, "got 0x%08x\n", hr);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_fontweight(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR ruW[] = {'r','u',0};
    IDWriteTextFormat *format, *fmt2;
    IDWriteTextLayout *layout;
    DWRITE_FONT_WEIGHT weight;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    FLOAT size;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&fmt2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    weight = IDWriteTextFormat_GetFontWeight(fmt2);
    ok(weight == DWRITE_FONT_WEIGHT_BOLD, "got %u\n", weight);

    range.startPosition = range.length = 0;
    hr = IDWriteTextLayout_GetFontWeight(layout, 0, &weight, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(range.startPosition == 0 && range.length == ~0u, "got %u, %u\n", range.startPosition, range.length);

    range.startPosition = 0;
    range.length = 6;
    hr = IDWriteTextLayout_SetFontWeight(layout, DWRITE_FONT_WEIGHT_NORMAL, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = range.length = 0;
    hr = IDWriteTextLayout_GetFontWeight(layout, 0, &weight, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(range.startPosition == 0 && range.length == 6, "got %u, %u\n", range.startPosition, range.length);

    /* IDWriteTextFormat methods output doesn't reflect layout changes */
    weight = IDWriteTextFormat_GetFontWeight(fmt2);
    ok(weight == DWRITE_FONT_WEIGHT_BOLD, "got %u\n", weight);

    range.length = 0;
    weight = DWRITE_FONT_WEIGHT_BOLD;
    hr = IDWriteTextLayout_GetFontWeight(layout, 0, &weight, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(weight == DWRITE_FONT_WEIGHT_NORMAL, "got %d\n", weight);
    ok(range.length == 6, "got %d\n", range.length);

    range.startPosition = 0;
    range.length = 6;
    hr = IDWriteTextLayout_SetFontWeight(layout, 1000, range);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 100.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxWidth(layout, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxWidth(layout, -1.0);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxWidth(layout, 100.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxWidth(layout);
    ok(size == 100.0, "got %.2f\n", size);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 100.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxHeight(layout, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxHeight(layout, -1.0);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 0.0, "got %.2f\n", size);

    hr = IDWriteTextLayout_SetMaxHeight(layout, 100.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = IDWriteTextLayout_GetMaxHeight(layout);
    ok(size == 100.0, "got %.2f\n", size);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(fmt2);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetInlineObject(void)
{
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR ruW[] = {'r','u',0};

    IDWriteInlineObject *inlineobj, *inlineobj2, *inlinetest;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range, r2;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &inlineobj);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &inlineobj2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(inlineobj, 1);
    EXPECT_REF(inlineobj2, 1);

    inlinetest = (void*)0x1;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == NULL, "got %p\n", inlinetest);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(inlineobj, 2);

    inlinetest = (void*)0x1;
    hr = IDWriteTextLayout_GetInlineObject(layout, 2, &inlinetest, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == NULL, "got %p\n", inlinetest);

    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 2, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    /* get from somewhere inside a range */
    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 1, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 2, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 1, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj2, "got %p\n", inlinetest);
    ok(r2.startPosition == 1 && r2.length == 1, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);
    EXPECT_REF(inlineobj2, 2);

    inlinetest = NULL;
    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 1, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 2, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);

    range.startPosition = 1;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(inlineobj, 2);

    r2.startPosition = r2.length = 100;
    hr = IDWriteTextLayout_GetInlineObject(layout, 0, &inlinetest, &r2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inlinetest == inlineobj, "got %p\n", inlinetest);
    ok(r2.startPosition == 0 && r2.length == 3, "got %d, %d\n", r2.startPosition, r2.length);
    IDWriteInlineObject_Release(inlinetest);

    EXPECT_REF(inlineobj, 2);
    EXPECT_REF(inlineobj2, 1);

    IDWriteTextLayout_Release(layout);

    EXPECT_REF(inlineobj, 1);

    IDWriteInlineObject_Release(inlineobj);
    IDWriteInlineObject_Release(inlineobj2);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

/* drawing calls sequence doesn't depend on run order, instead all runs are
   drawn first, inline objects next and then underline/strikes */
static const struct drawcall_entry draw_seq[] = {
    { DRAW_GLYPHRUN, {'s',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'r','i',0}, {'r','u',0}, 2 },
    { DRAW_GLYPHRUN|DRAW_EFFECT, {'n',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'g',0}, {'r','u',0}, 1 },
    { DRAW_INLINE },
    { DRAW_UNDERLINE, {0}, {'r','u',0} },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_trimmed_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq2[] = {
    { DRAW_GLYPHRUN, {'s',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'t',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'r',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'i',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'n',0}, {'r','u',0}, 1 },
    { DRAW_GLYPHRUN, {'g',0}, {'r','u',0}, 1 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq3[] = {
    { DRAW_GLYPHRUN, {0x202a,0x202c,0}, {'r','u',0}, 0 },
    { DRAW_GLYPHRUN, {'a','b',0}, {'r','u',0}, 2 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq4[] = {
    { DRAW_GLYPHRUN, {'s','t','r',0}, {'r','u',0}, 3 },
    { DRAW_GLYPHRUN, {'i','n','g',0}, {'r','u',0}, 3  },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_seq5[] = {
    { DRAW_GLYPHRUN, {'s','t',0}, {'r','u',0}, 2 },
    { DRAW_GLYPHRUN, {'r','i',0}, {'r','u',0}, 2 },
    { DRAW_GLYPHRUN, {'n','g',0}, {'r','u',0}, 2 },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry empty_seq[] = {
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_single_run_seq[] = {
    { DRAW_GLYPHRUN, {'s','t','r','i','n','g',0}, {'r','u',0}, 6 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draw_ltr_reordered_run_seq[] = {
    { DRAW_GLYPHRUN, {'1','2','3','-','5','2',0}, {'r','u',0}, 6 },
    { DRAW_GLYPHRUN, {0x64a,0x64f,0x633,0x627,0x648,0x650,0x64a,0}, {'r','u',0}, 7, 1 },
    { DRAW_GLYPHRUN, {'7','1',0}, {'r','u',0}, 2, 2 },
    { DRAW_GLYPHRUN, {'.',0}, {'r','u',0}, 1 },
    { DRAW_LAST_KIND }
};

static void test_Draw(void)
{
    static const WCHAR str3W[] = {'1','2','3','-','5','2',0x64a,0x64f,0x633,0x627,0x648,0x650,
        0x64a,'7','1','.',0};
    static const WCHAR strW[] = {'s','t','r','i','n','g',0};
    static const WCHAR str2W[] = {0x202a,0x202c,'a','b',0};
    static const WCHAR ruW[] = {'r','u',0};
    IDWriteInlineObject *inlineobj;
    struct renderer_context ctxt;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    DWRITE_TEXT_METRICS tm;
    DWRITE_MATRIX m;
    HRESULT hr;

    factory = create_factory();

    memset(&ctxt, 0, sizeof(ctxt));
    ctxt.snapping_disabled = TRUE;

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, ruW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 100.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &inlineobj);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 5;
    range.length = 1;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetInlineObject(layout, inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 4;
    range.length = 1;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, (IUnknown*)inlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq, "draw test", FALSE);
    IDWriteTextLayout_Release(layout);

    /* with reduced width DrawGlyphRun() is called for every line */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 5.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq2, "draw test 2", TRUE);
    hr = IDWriteTextLayout_GetMetrics(layout, &tm);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(tm.lineCount == 6, "got %u\n", tm.lineCount);
    IDWriteTextLayout_Release(layout);

    /* string with control characters */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 4, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq3, "draw test 3", FALSE);
    IDWriteTextLayout_Release(layout);

    /* strikethrough splits ranges from renderer point of view, but doesn't break
       shaping */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);

    range.startPosition = 0;
    range.length = 3;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq4, "draw test 4", FALSE);
    IDWriteTextLayout_Release(layout);

    /* strikethrough somewhere in the middle */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 6, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);

    range.startPosition = 2;
    range.length = 2;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_seq5, "draw test 5", FALSE);
    IDWriteTextLayout_Release(layout);

    /* empty string */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, empty_seq, "draw test 6", FALSE);
    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = TRUE;

    /* different parameter combinations with gdi-compatible layout */
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, TRUE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 7", FALSE);

    /* text alignment keeps pixel-aligned origin */
    hr = IDWriteTextLayout_GetMetrics(layout, &tm);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(tm.width == floorf(tm.width), "got %f\n", tm.width);

    hr = IDWriteTextLayout_SetMaxWidth(layout, tm.width + 3.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ctxt.originX = ctxt.originY = 0.0;
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 7", FALSE);
    ok(ctxt.originX != 0.0 && ctxt.originX == floorf(ctxt.originX), "got %f\n", ctxt.originX);

    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = FALSE;

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 8", FALSE);
    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = TRUE;

    m.m11 = m.m22 = 2.0;
    m.m12 = m.m21 = m.dx = m.dy = 0.0;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, &m, TRUE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 9", FALSE);
    IDWriteTextLayout_Release(layout);

    ctxt.gdicompat = TRUE;
    ctxt.use_gdi_natural = FALSE;

    m.m11 = m.m22 = 2.0;
    m.m12 = m.m21 = m.dx = m.dy = 0.0;
    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 6, format, 100.0, 100.0, 1.0, &m, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_single_run_seq, "draw test 10", FALSE);
    IDWriteTextLayout_Release(layout);

    IDWriteInlineObject_Release(inlineobj);

    /* text that triggers bidi run reordering */
    hr = IDWriteFactory_CreateTextLayout(factory, str3W, lstrlenW(str3W), format, 1000.0f, 100.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ctxt.gdicompat = FALSE;
    ctxt.use_gdi_natural = FALSE;
    ctxt.snapping_disabled = TRUE;

    hr = IDWriteTextLayout_SetReadingDirection(layout, DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK, "Failed to set reading direction, hr %#x.\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_ltr_reordered_run_seq, "draw test 11", FALSE);

    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_typography(void)
{
    DWRITE_FONT_FEATURE feature;
    IDWriteTypography *typography;
    IDWriteFactory *factory;
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTypography(factory, &typography);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    feature.nameTag = DWRITE_FONT_FEATURE_TAG_KERNING;
    feature.parameter = 1;
    hr = IDWriteTypography_AddFontFeature(typography, feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = IDWriteTypography_GetFontFeatureCount(typography);
    ok(count == 1, "got %u\n", count);

    /* duplicated features work just fine */
    feature.nameTag = DWRITE_FONT_FEATURE_TAG_KERNING;
    feature.parameter = 0;
    hr = IDWriteTypography_AddFontFeature(typography, feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = IDWriteTypography_GetFontFeatureCount(typography);
    ok(count == 2, "got %u\n", count);

    memset(&feature, 0xcc, sizeof(feature));
    hr = IDWriteTypography_GetFontFeature(typography, 0, &feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(feature.nameTag == DWRITE_FONT_FEATURE_TAG_KERNING, "got tag %x\n", feature.nameTag);
    ok(feature.parameter == 1, "got %u\n", feature.parameter);

    memset(&feature, 0xcc, sizeof(feature));
    hr = IDWriteTypography_GetFontFeature(typography, 1, &feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(feature.nameTag == DWRITE_FONT_FEATURE_TAG_KERNING, "got tag %x\n", feature.nameTag);
    ok(feature.parameter == 0, "got %u\n", feature.parameter);

    hr = IDWriteTypography_GetFontFeature(typography, 2, &feature);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* duplicated with same parameter value */
    feature.nameTag = DWRITE_FONT_FEATURE_TAG_KERNING;
    feature.parameter = 0;
    hr = IDWriteTypography_AddFontFeature(typography, feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = IDWriteTypography_GetFontFeatureCount(typography);
    ok(count == 3, "got %u\n", count);

    memset(&feature, 0xcc, sizeof(feature));
    hr = IDWriteTypography_GetFontFeature(typography, 2, &feature);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(feature.nameTag == DWRITE_FONT_FEATURE_TAG_KERNING, "got tag %x\n", feature.nameTag);
    ok(feature.parameter == 0, "got %u\n", feature.parameter);

    IDWriteTypography_Release(typography);
    IDWriteFactory_Release(factory);
}

static void test_GetClusterMetrics(void)
{
    static const WCHAR str_white_spaceW[] = {
        /* BK - FORM FEED, LINE TABULATION, LINE SEP, PARA SEP */ 0xc, 0xb, 0x2028, 0x2029,
        /* ZW - ZERO WIDTH SPACE */ 0x200b,
        /* SP - SPACE  */ 0x20
    };
    static const WCHAR str5W[] = {'a','\r','b','\n','c','\n','\r','d','\r','\n','e',0xb,'f',0xc,
        'g',0x0085,'h',0x2028,'i',0x2029,0xad,0xa,0};
    static const WCHAR str3W[] = {0x2066,')',')',0x661,'(',0x627,')',0};
    static const WCHAR str2W[] = {0x202a,0x202c,'a',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    static const WCHAR str4W[] = {'a',' ',0};
    static const WCHAR str6W[] = {'a',' ','b',0};
    DWRITE_INLINE_OBJECT_METRICS inline_metrics;
    DWRITE_CLUSTER_METRICS metrics[22];
    DWRITE_TEXT_METRICS text_metrics;
    DWRITE_TRIMMING trimming_options;
    IDWriteTextLayout1 *layout1;
    IDWriteInlineObject *trimm;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_LINE_METRICS line;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    UINT32 count, i;
    FLOAT width;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str3W, 7, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    hr = IDWriteTextLayout_GetClusterMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 7, "got %u\n", count);
    IDWriteTextLayout_Release(layout);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);

    /* check every cluster width */
    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);
    for (i = 0; i < count; i++) {
        ok(metrics[i].width > 0.0, "%u: got width %.2f\n", i, metrics[i].width);
        ok(metrics[i].length == 1, "%u: got length %u\n", i, metrics[i].length);
    }

    /* apply spacing and check widths again */
    if (IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout1, (void**)&layout1) == S_OK) {
        DWRITE_CLUSTER_METRICS metrics2[4];
        FLOAT leading, trailing, min_advance;
        DWRITE_TEXT_RANGE r;

        leading = trailing = min_advance = 2.0;
        hr = IDWriteTextLayout1_GetCharacterSpacing(layout1, 500, &leading, &trailing,
            &min_advance, &r);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(leading == 0.0 && trailing == 0.0 && min_advance == 0.0,
            "got %.2f, %.2f, %.2f\n", leading, trailing, min_advance);
        ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);

        leading = trailing = min_advance = 2.0;
        hr = IDWriteTextLayout1_GetCharacterSpacing(layout1, 0, &leading, &trailing,
            &min_advance, NULL);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(leading == 0.0 && trailing == 0.0 && min_advance == 0.0,
            "got %.2f, %.2f, %.2f\n", leading, trailing, min_advance);

        r.startPosition = 0;
        r.length = 4;
        hr = IDWriteTextLayout1_SetCharacterSpacing(layout1, 10.0, 15.0, 0.0, r);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        count = 0;
        hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics2, ARRAY_SIZE(metrics2), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(count == 4, "got %u\n", count);
        for (i = 0; i < count; i++) {
todo_wine
            ok(metrics2[i].width > metrics[i].width, "%u: got width %.2f, was %.2f\n", i, metrics2[i].width,
                metrics[i].width);
            ok(metrics2[i].length == 1, "%u: got length %u\n", i, metrics2[i].length);
        }

        /* back to defaults */
        r.startPosition = 0;
        r.length = 4;
        hr = IDWriteTextLayout1_SetCharacterSpacing(layout1, 0.0, 0.0, 0.0, r);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        /* negative advance limit */
        r.startPosition = 0;
        r.length = 4;
        hr = IDWriteTextLayout1_SetCharacterSpacing(layout1, 0.0, 0.0, -10.0, r);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        IDWriteTextLayout1_Release(layout1);
    }
    else
        win_skip("IDWriteTextLayout1 is not supported, cluster spacing test skipped.\n");

    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &trimm);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, trimm, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* inline object takes a separate cluster, replaced codepoints number doesn't matter */
    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);

    count = 0;
    memset(&metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);
    ok(metrics[0].length == 2, "got %u\n", metrics[0].length);

    hr = IDWriteInlineObject_GetMetrics(trimm, &inline_metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(inline_metrics.width > 0.0 && inline_metrics.width == metrics[0].width, "got %.2f, expected %.2f\n",
        inline_metrics.width, metrics[0].width);

    IDWriteTextLayout_Release(layout);

    /* text with non-visual control codes */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 3, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* bidi control codes take a separate cluster */
    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);

    ok(metrics[0].width == 0.0, "got %.2f\n", metrics[0].width);
    ok(metrics[0].length == 1, "got %d\n", metrics[0].length);
    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[0].isSoftHyphen == 0, "got %d\n", metrics[0].isSoftHyphen);
    ok(metrics[0].isRightToLeft == 0, "got %d\n", metrics[0].isRightToLeft);

    ok(metrics[1].width == 0.0, "got %.2f\n", metrics[1].width);
    ok(metrics[1].length == 1, "got %d\n", metrics[1].length);
    ok(metrics[1].canWrapLineAfter == 0, "got %d\n", metrics[1].canWrapLineAfter);
    ok(metrics[1].isWhitespace == 0, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[1].isNewline == 0, "got %d\n", metrics[1].isNewline);
    ok(metrics[1].isSoftHyphen == 0, "got %d\n", metrics[1].isSoftHyphen);
    ok(metrics[1].isRightToLeft == 0, "got %d\n", metrics[1].isRightToLeft);

    ok(metrics[2].width > 0.0, "got %.2f\n", metrics[2].width);
    ok(metrics[2].length == 1, "got %d\n", metrics[2].length);
    ok(metrics[2].canWrapLineAfter == 1, "got %d\n", metrics[2].canWrapLineAfter);
    ok(metrics[2].isWhitespace == 0, "got %d\n", metrics[2].isWhitespace);
    ok(metrics[2].isNewline == 0, "got %d\n", metrics[2].isNewline);
    ok(metrics[2].isSoftHyphen == 0, "got %d\n", metrics[2].isSoftHyphen);
    ok(metrics[2].isRightToLeft == 0, "got %d\n", metrics[2].isRightToLeft);

    IDWriteTextLayout_Release(layout);

    /* single inline object that fails to report its metrics */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 4;
    hr = IDWriteTextLayout_SetInlineObject(layout, &testinlineobj, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    /* object sets a width to 123.0, but returns failure from GetMetrics() */
    ok(metrics[0].width == 0.0, "got %.2f\n", metrics[0].width);
    ok(metrics[0].length == 4, "got %d\n", metrics[0].length);
    ok(metrics[0].canWrapLineAfter == 1, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[0].isSoftHyphen == 0, "got %d\n", metrics[0].isSoftHyphen);
    ok(metrics[0].isRightToLeft == 0, "got %d\n", metrics[0].isRightToLeft);

    /* now set two inline object for [0,1] and [2,3], both fail to report break conditions */
    range.startPosition = 2;
    range.length = 2;
    hr = IDWriteTextLayout_SetInlineObject(layout, &testinlineobj2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);

    ok(metrics[0].width == 0.0, "got %.2f\n", metrics[0].width);
    ok(metrics[0].length == 2, "got %d\n", metrics[0].length);
    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[0].isSoftHyphen == 0, "got %d\n", metrics[0].isSoftHyphen);
    ok(metrics[0].isRightToLeft == 0, "got %d\n", metrics[0].isRightToLeft);

    ok(metrics[1].width == 0.0, "got %.2f\n", metrics[1].width);
    ok(metrics[1].length == 2, "got %d\n", metrics[1].length);
    ok(metrics[1].canWrapLineAfter == 1, "got %d\n", metrics[1].canWrapLineAfter);
    ok(metrics[1].isWhitespace == 0, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[1].isNewline == 0, "got %d\n", metrics[1].isNewline);
    ok(metrics[1].isSoftHyphen == 0, "got %d\n", metrics[1].isSoftHyphen);
    ok(metrics[1].isRightToLeft == 0, "got %d\n", metrics[1].isRightToLeft);

    IDWriteTextLayout_Release(layout);

    /* zero length string */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 1;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 0, "got %u\n", count);
    IDWriteTextLayout_Release(layout);

    /* whitespace */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 2, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[0].isWhitespace == 0, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[1].isWhitespace == 1, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[1].canWrapLineAfter == 1, "got %d\n", metrics[1].canWrapLineAfter);
    IDWriteTextLayout_Release(layout);

    /* layout is fully covered by inline object with after condition DWRITE_BREAK_CONDITION_MAY_NOT_BREAK */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 2, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = ~0u;
    hr = IDWriteTextLayout_SetInlineObject(layout, &testinlineobj3, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].canWrapLineAfter == 1, "got %d\n", metrics[0].canWrapLineAfter);

    IDWriteTextLayout_Release(layout);

    /* compare natural cluster width with gdi layout */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 1, format, 100.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].width != floorf(metrics[0].width), "got %f\n", metrics[0].width);

    IDWriteTextLayout_Release(layout);

    hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, str4W, 1, format, 100.0, 100.0, 1.0, NULL, FALSE, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].width == floorf(metrics[0].width), "got %f\n", metrics[0].width);

    IDWriteTextLayout_Release(layout);

    /* isNewline tests */
    hr = IDWriteFactory_CreateTextLayout(factory, str5W, lstrlenW(str5W), format, 100.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 22, "got %u\n", count);

    ok(metrics[1].isNewline == 1, "got %d\n", metrics[1].isNewline);
    ok(metrics[3].isNewline == 1, "got %d\n", metrics[3].isNewline);
    ok(metrics[5].isNewline == 1, "got %d\n", metrics[5].isNewline);
    ok(metrics[6].isNewline == 1, "got %d\n", metrics[6].isNewline);
    ok(metrics[9].isNewline == 1, "got %d\n", metrics[9].isNewline);
    ok(metrics[11].isNewline == 1, "got %d\n", metrics[11].isNewline);
    ok(metrics[13].isNewline == 1, "got %d\n", metrics[13].isNewline);
    ok(metrics[15].isNewline == 1, "got %d\n", metrics[15].isNewline);
    ok(metrics[17].isNewline == 1, "got %d\n", metrics[17].isNewline);
    ok(metrics[19].isNewline == 1, "got %d\n", metrics[19].isNewline);
    ok(metrics[21].isNewline == 1, "got %d\n", metrics[21].isNewline);

    ok(metrics[0].isNewline == 0, "got %d\n", metrics[0].isNewline);
    ok(metrics[2].isNewline == 0, "got %d\n", metrics[2].isNewline);
    ok(metrics[4].isNewline == 0, "got %d\n", metrics[4].isNewline);
    ok(metrics[7].isNewline == 0, "got %d\n", metrics[7].isNewline);
    ok(metrics[8].isNewline == 0, "got %d\n", metrics[8].isNewline);
    ok(metrics[10].isNewline == 0, "got %d\n", metrics[10].isNewline);
    ok(metrics[12].isNewline == 0, "got %d\n", metrics[12].isNewline);
    ok(metrics[14].isNewline == 0, "got %d\n", metrics[14].isNewline);
    ok(metrics[16].isNewline == 0, "got %d\n", metrics[16].isNewline);
    ok(metrics[18].isNewline == 0, "got %d\n", metrics[18].isNewline);
    ok(metrics[20].isNewline == 0, "got %d\n", metrics[20].isNewline);

    for (i = 0; i < count; i++) {
        ok(metrics[i].length == 1, "%d: got %d\n", i, metrics[i].length);
        ok(metrics[i].isSoftHyphen == (i == count - 2), "%d: got %d\n", i, metrics[i].isSoftHyphen);
        if (metrics[i].isSoftHyphen)
            ok(!metrics[i].isWhitespace, "%u: got %d\n", i, metrics[i].isWhitespace);
        if (metrics[i].isNewline) {
            ok(metrics[i].width == 0.0f, "%u: got width %f\n", i, metrics[i].width);
            ok(metrics[i].isWhitespace == 1, "%u: got %d\n", i, metrics[i].isWhitespace);
            ok(metrics[i].canWrapLineAfter == 1, "%u: got %d\n", i, metrics[i].canWrapLineAfter);
        }
    }

    IDWriteTextLayout_Release(layout);

    /* Test whitespace resolution from linebreaking classes BK, ZW, and SP */
    hr = IDWriteFactory_CreateTextLayout(factory, str_white_spaceW, ARRAY_SIZE(str_white_spaceW), format,
        100.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 20, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 6, "got %u\n", count);

    ok(metrics[0].isWhitespace == 1, "got %d\n", metrics[0].isWhitespace);
    ok(metrics[1].isWhitespace == 1, "got %d\n", metrics[1].isWhitespace);
    ok(metrics[2].isWhitespace == 1, "got %d\n", metrics[2].isWhitespace);
    ok(metrics[3].isWhitespace == 1, "got %d\n", metrics[3].isWhitespace);
    ok(metrics[4].isWhitespace == 0, "got %d\n", metrics[4].isWhitespace);
    ok(metrics[5].isWhitespace == 1, "got %d\n", metrics[5].isWhitespace);

    IDWriteTextLayout_Release(layout);

    /* trigger line trimming */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, lstrlenW(strW), format, 100.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);

    hr = IDWriteTextLayout_GetMetrics(layout, &text_metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    width = metrics[0].width + inline_metrics.width;
    ok(width < text_metrics.width, "unexpected trimming sign width\n");

    /* enable trimming, reduce layout width so only first cluster and trimming sign fits */
    trimming_options.granularity = DWRITE_TRIMMING_GRANULARITY_CHARACTER;
    trimming_options.delimiter = 0;
    trimming_options.delimiterCount = 0;
    hr = IDWriteTextLayout_SetTrimming(layout, &trimming_options, trimm);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetMaxWidth(layout, width);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);

    hr = IDWriteTextLayout_GetLineMetrics(layout, &line, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(line.length == 4, "got %u\n", line.length);
    ok(line.isTrimmed, "got %d\n", line.isTrimmed);

    IDWriteTextLayout_Release(layout);

    /* NO_WRAP, check cluster wrapping attribute. */
    hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str6W, lstrlenW(str6W), format, 1000.0f, 200.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 3, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 3, "got %u\n", count);

    ok(metrics[0].canWrapLineAfter == 0, "got %d\n", metrics[0].canWrapLineAfter);
    ok(metrics[1].canWrapLineAfter == 1, "got %d\n", metrics[1].canWrapLineAfter);
    ok(metrics[2].canWrapLineAfter == 1, "got %d\n", metrics[2].canWrapLineAfter);

    IDWriteTextLayout_Release(layout);

    /* Single cluster layout, trigger trimming. */
    hr = IDWriteFactory_CreateTextLayout(factory, str6W, 1, format, 1000.0f, 200.0f, &layout);
    ok(hr == S_OK, "Failed to create layout, hr %#x.\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(count == 1, "Unexpected cluster count %u.\n", count);

    hr = IDWriteTextLayout_SetMaxWidth(layout, metrics[0].width / 2.0f);
    ok(hr == S_OK, "Failed to set layout width, hr %#x.\n", hr);

    trimming_options.granularity = DWRITE_TRIMMING_GRANULARITY_CHARACTER;
    trimming_options.delimiter = 0;
    trimming_options.delimiterCount = 0;
    hr = IDWriteTextLayout_SetTrimming(layout, &trimming_options, trimm);
    ok(hr == S_OK, "Failed to set trimming options, hr %#x.\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(count == 1, "Unexpected cluster count %u.\n", count);

    hr = IDWriteTextLayout_GetLineMetrics(layout, &line, 1, &count);
    ok(hr == S_OK, "Failed to get line metrics, hr %#x.\n", hr);
    ok(count == 1, "Unexpected line count %u.\n", count);
    ok(line.length == 1, "Unexpected line length %u.\n", line.length);
    ok(line.isTrimmed, "Unexpected trimming flag %x.\n", line.isTrimmed);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "Draw() failed, hr %#x.\n", hr);
    ok_sequence(sequences, RENDERER_ID, draw_trimmed_seq, "Trimmed draw test", FALSE);

    IDWriteTextLayout_Release(layout);

    IDWriteInlineObject_Release(trimm);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetLocaleName(void)
{
    static const WCHAR eNuSW[] = {'e','N','-','u','S',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    WCHAR buffW[LOCALE_NAME_MAX_LENGTH + ARRAY_SIZE(strW)];
    IDWriteTextFormat *format, *format2;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    /* create format with mixed case locale name, get it back */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, eNuSW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_GetLocaleName(format, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat, (void**)&format2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_GetLocaleName(format2, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));

    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));

    IDWriteTextFormat_Release(format2);
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetLocaleName(layout, enusW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLocaleName(layout, NULL, range);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* invalid locale name is allowed */
    hr = IDWriteTextLayout_SetLocaleName(layout, strW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetLocaleName(layout, 0, NULL, 0, NULL);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    if (0) /* crashes on native */
        hr = IDWriteTextLayout_GetLocaleName(layout, 0, NULL, 1, NULL);

    buffW[0] = 0;
    range.length = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, strW), "got %s\n", wine_dbgstr_w(buffW));
    ok(range.startPosition == 0 && range.length == 1, "got %u,%u\n", range.startPosition, range.length);

    /* get with a shorter buffer */
    buffW[0] = 0xa;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, 1, NULL);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(buffW[0] == 0, "got %x\n", buffW[0]);

    /* name is too long */
    lstrcpyW(buffW, strW);
    while (lstrlenW(buffW) <= LOCALE_NAME_MAX_LENGTH)
        lstrcatW(buffW, strW);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetLocaleName(layout, buffW, range);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    buffW[0] = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, strW), "got %s\n", wine_dbgstr_w(buffW));

    /* set initial locale name for whole text, except with a different casing */
    range.startPosition = 0;
    range.length = 4;
    hr = IDWriteTextLayout_SetLocaleName(layout, eNuSW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    buffW[0] = 0;
    range.length = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 0, buffW, ARRAY_SIZE(buffW), &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));
    ok((range.startPosition == 0 && range.length == ~0u) ||
        broken(range.startPosition == 0 && range.length == 4) /* vista/win7 */, "got %u,%u\n", range.startPosition, range.length);

    /* check what's returned for positions after the text */
    buffW[0] = 0;
    range.length = 0;
    hr = IDWriteTextLayout_GetLocaleName(layout, 100, buffW, ARRAY_SIZE(buffW), &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, enusW), "got %s\n", wine_dbgstr_w(buffW));
    ok((range.startPosition == 0 && range.length == ~0u) ||
        broken(range.startPosition == 4 && range.length == ~0u-4) /* vista/win7 */, "got %u,%u\n", range.startPosition, range.length);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetPairKerning(void)
{
    static const WCHAR strW[] = {'a','e',0x0300,'d',0}; /* accent grave */
    DWRITE_CLUSTER_METRICS clusters[4];
    IDWriteTextLayout1 *layout1;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    BOOL kerning;
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout1, (void**)&layout1);
    IDWriteTextLayout_Release(layout);

    if (hr != S_OK) {
        win_skip("SetPairKerning() is not supported.\n");
        IDWriteFactory_Release(factory);
        return;
    }

if (0) { /* crashes on native */
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, NULL, NULL);
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, NULL, &range);
}

    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, &kerning, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 0;
    kerning = TRUE;
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, &kerning, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!kerning, "got %d\n", kerning);
    ok(range.length == ~0u, "got %u\n", range.length);

    count = 0;
    hr = IDWriteTextLayout1_GetClusterMetrics(layout1, clusters, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(count == 3, "got %u\n", count);
if (count == 3) {
    ok(clusters[0].length == 1, "got %u\n", clusters[0].length);
    ok(clusters[1].length == 2, "got %u\n", clusters[1].length);
    ok(clusters[2].length == 1, "got %u\n", clusters[2].length);
}
    /* pair kerning flag participates in itemization - combining characters
       breaks */
    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout1_SetPairKerning(layout1, 2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    kerning = FALSE;
    hr = IDWriteTextLayout1_GetPairKerning(layout1, 0, &kerning, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(kerning == TRUE, "got %d\n", kerning);

    count = 0;
    hr = IDWriteTextLayout1_GetClusterMetrics(layout1, clusters, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);
    ok(clusters[0].length == 1, "got %u\n", clusters[0].length);
    ok(clusters[1].length == 1, "got %u\n", clusters[1].length);
    ok(clusters[2].length == 1, "got %u\n", clusters[2].length);
    ok(clusters[3].length == 1, "got %u\n", clusters[3].length);

    IDWriteTextLayout1_Release(layout1);
    IDWriteFactory_Release(factory);
}

static void test_SetVerticalGlyphOrientation(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_VERTICAL_GLYPH_ORIENTATION orientation;
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    IDWriteTextLayout_Release(layout);

    if (hr != S_OK) {
        win_skip("SetVerticalGlyphOrientation() is not supported.\n");
        IDWriteFactory_Release(factory);
        return;
    }

    orientation = IDWriteTextLayout2_GetVerticalGlyphOrientation(layout2);
    ok(orientation == DWRITE_VERTICAL_GLYPH_ORIENTATION_DEFAULT, "got %d\n", orientation);

    hr = IDWriteTextLayout2_SetVerticalGlyphOrientation(layout2, DWRITE_VERTICAL_GLYPH_ORIENTATION_STACKED+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    IDWriteTextLayout2_Release(layout2);
    IDWriteFactory_Release(factory);
}

static void test_DetermineMinWidth(void)
{
    struct minwidth_test {
        const WCHAR text[10];    /* text to create a layout for */
        const WCHAR mintext[10]; /* text that represents sequence of minimal width */
    } minwidth_tests[] = {
        { {' ','a','b',' ',0}, {'a','b',0} },
        { {'a','\n',' ',' ',0}, {'a',0} },
        { {'a','\n',' ',' ','b',0}, {'b',0} },
        { {'a','b','c','\n',' ',' ','b',0}, {'a','b','c',0} },
    };
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_CLUSTER_METRICS metrics[10];
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    UINT32 count, i, j;
    FLOAT minwidth;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, lstrlenW(strW), format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_DetermineMinWidth(layout, NULL);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    /* empty string */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 100.0f, 100.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    minwidth = 1.0f;
    hr = IDWriteTextLayout_DetermineMinWidth(layout, &minwidth);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(minwidth == 0.0f, "got %f\n", minwidth);
    IDWriteTextLayout_Release(layout);

    for (i = 0; i < ARRAY_SIZE(minwidth_tests); i++) {
        FLOAT width = 0.0f;

        /* measure expected width */
        hr = IDWriteFactory_CreateTextLayout(factory, minwidth_tests[i].mintext, lstrlenW(minwidth_tests[i].mintext), format, 1000.0f, 1000.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetClusterMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        for (j = 0; j < count; j++)
            width += metrics[j].width;

        IDWriteTextLayout_Release(layout);

        hr = IDWriteFactory_CreateTextLayout(factory, minwidth_tests[i].text, lstrlenW(minwidth_tests[i].text), format, 1000.0f, 1000.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        minwidth = 0.0f;
        hr = IDWriteTextLayout_DetermineMinWidth(layout, &minwidth);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(minwidth == width, "test %u: expected width %f, got %f\n", i, width, minwidth);

        IDWriteTextLayout_Release(layout);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontSize(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    FLOAT size;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* negative/zero size */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, -15.0, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetFontSize(layout, 0.0, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 0, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 10.0, "got %.2f\n", size);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 15.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontSize(layout, 123.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 1, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(size == 15.0, "got %.2f\n", size);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontSize(layout, 15.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 1, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(size == 15.0, "got %.2f\n", size);

    size = 0.0;
    hr = IDWriteTextLayout_GetFontSize(layout, 0, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 15.0, "got %.2f\n", size);

    size = 15.0;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontSize(layout, 20, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 4 && r.length == ~0u-4, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 10.0, "got %.2f\n", size);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontSize(layout, 25.0, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    size = 15.0;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontSize(layout, 100, &size, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(size == 25.0, "got %.2f\n", size);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontFamilyName(void)
{
    static const WCHAR taHomaW[] = {'T','a','H','o','m','a',0};
    static const WCHAR arialW[] = {'A','r','i','a','l',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    WCHAR nameW[50];
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* NULL name */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, NULL, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    nameW[0] = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);

    /* set name only different in casing */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, taHomaW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, arialW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 0;
    nameW[0] = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(nameW, taHomaW), "got %s\n", wine_dbgstr_w(nameW));
    ok(r.startPosition == 1 && r.length == 1, "got %u, %u\n", r.startPosition, r.length);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, arialW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 1 && r.length == 1, "got %u, %u\n", r.startPosition, r.length);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontFamilyName(layout, arialW, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    nameW[0] = 0;
    hr = IDWriteTextLayout_GetFontFamilyName(layout, 1, nameW, ARRAY_SIZE(nameW), &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(!lstrcmpW(nameW, arialW), "got name %s\n", wine_dbgstr_w(nameW));

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontStyle(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_FONT_STYLE style;
    DWRITE_TEXT_RANGE r;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* invalid style value */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_ITALIC+1, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_GetFontStyle(layout, 0, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_NORMAL, "got %d\n", style);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_ITALIC, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_NORMAL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    style = DWRITE_FONT_STYLE_NORMAL;
    hr = IDWriteTextLayout_GetFontStyle(layout, 1, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(style == DWRITE_FONT_STYLE_ITALIC, "got %d\n", style);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_OBLIQUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    style = DWRITE_FONT_STYLE_ITALIC;
    hr = IDWriteTextLayout_GetFontStyle(layout, 1, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);

    style = DWRITE_FONT_STYLE_ITALIC;
    hr = IDWriteTextLayout_GetFontStyle(layout, 0, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);

    style = DWRITE_FONT_STYLE_ITALIC;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStyle(layout, 20, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 4 && r.length == ~0u-4, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_NORMAL, "got %d\n", style);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStyle(layout, DWRITE_FONT_STYLE_OBLIQUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    style = DWRITE_FONT_STYLE_NORMAL;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStyle(layout, 100, &style, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(style == DWRITE_FONT_STYLE_OBLIQUE, "got %d\n", style);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFontStretch(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_FONT_STRETCH stretch;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* invalid stretch value */
    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_ULTRA_EXPANDED+1, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 0, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_NORMAL, "got %d\n", stretch);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_CONDENSED, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* zero length range */
    r.startPosition = 1;
    r.length = 0;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_NORMAL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 1, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(stretch == DWRITE_FONT_STRETCH_CONDENSED, "got %d\n", stretch);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_EXPANDED, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 1, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(stretch == DWRITE_FONT_STRETCH_EXPANDED, "got %d\n", stretch);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    hr = IDWriteTextLayout_GetFontStretch(layout, 0, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_EXPANDED, "got %d\n", stretch);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStretch(layout, 20, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 4 && r.length == ~0u-4, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_NORMAL, "got %d\n", stretch);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_EXPANDED, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    stretch = DWRITE_FONT_STRETCH_UNDEFINED;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetFontStretch(layout, 100, &stretch, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(stretch == DWRITE_FONT_STRETCH_EXPANDED, "got %d\n", stretch);

    /* trying to set undefined value */
    r.startPosition = 0;
    r.length = 2;
    hr = IDWriteTextLayout_SetFontStretch(layout, DWRITE_FONT_STRETCH_UNDEFINED, r);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetStrikethrough(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE r;
    BOOL value;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 0;
    value = TRUE;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 0, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 0 && r.length == ~0u, "got %u, %u\n", r.startPosition, r.length);
    ok(value == FALSE, "got %d\n", value);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    value = FALSE;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 1, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(value == TRUE, "got %d\n", value);
    ok(r.startPosition == 1 && r.length == 1, "got %u, %u\n", r.startPosition, r.length);

    value = TRUE;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 20, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 2 && r.length == ~0u-2, "got %u, %u\n", r.startPosition, r.length);
    ok(value == FALSE, "got %d\n", value);

    r.startPosition = 100;
    r.length = 4;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    value = FALSE;
    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetStrikethrough(layout, 100, &value, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 4, "got %u, %u\n", r.startPosition, r.length);
    ok(value == TRUE, "got %d\n", value);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_GetMetrics(void)
{
    static const WCHAR str2W[] = {0x2066,')',')',0x661,'(',0x627,')',0};
    static const WCHAR strW[] = {'a','b','c','d',0};
    static const WCHAR str3W[] = {'a',0};
    static const WCHAR str4W[] = {' ',0};
    DWRITE_CLUSTER_METRICS clusters[4];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    UINT32 count, i;
    FLOAT width;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 4, "got %u\n", count);
    for (i = 0, width = 0.0; i < count; i++)
        width += clusters[i].width;

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == width, "got %.2f, expected %.2f\n", metrics.width, width);
    ok(metrics.widthIncludingTrailingWhitespace == width, "got %.2f, expected %.2f\n",
        metrics.widthIncludingTrailingWhitespace, width);
    ok(metrics.height > 0.0, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.maxBidiReorderingDepth == 1, "got %u\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "got %u\n", metrics.lineCount);

    IDWriteTextLayout_Release(layout);

    /* a string with more complex bidi sequence */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 7, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(&metrics, 0xcc, sizeof(metrics));
    metrics.maxBidiReorderingDepth = 0;
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width > 0.0, "got %.2f\n", metrics.width);
    ok(metrics.widthIncludingTrailingWhitespace > 0.0, "got %.2f\n", metrics.widthIncludingTrailingWhitespace);
    ok(metrics.height > 0.0, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "got %.2f\n", metrics.layoutHeight);
todo_wine
    ok(metrics.maxBidiReorderingDepth > 1, "got %u\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "got %u\n", metrics.lineCount);

    IDWriteTextLayout_Release(layout);

    /* single cluster layout */
    hr = IDWriteFactory_CreateTextLayout(factory, str3W, 1, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f, expected %.2f\n", metrics.width, clusters[0].width);
    ok(metrics.widthIncludingTrailingWhitespace == clusters[0].width, "got %.2f\n", metrics.widthIncludingTrailingWhitespace);
    ok(metrics.height > 0.0, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.maxBidiReorderingDepth == 1, "got %u\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "got %u\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    /* Whitespace only. */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 1, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %#x.\n", hr);

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "Failed to get layout metrics, hr %#x.\n", hr);
    ok(metrics.left == 0.0f, "Unexpected value for left %f.\n", metrics.left);
    ok(metrics.top == 0.0f, "Unexpected value for top %f.\n", metrics.top);
    ok(metrics.width == 0.0f, "Unexpected width %f.\n", metrics.width);
    ok(metrics.widthIncludingTrailingWhitespace > 0.0f, "Unexpected full width %f.\n",
            metrics.widthIncludingTrailingWhitespace);
    ok(metrics.height > 0.0, "Unexpected height %f.\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "Unexpected box width %f.\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 1000.0, "Unexpected box height %f.\n", metrics.layoutHeight);
    ok(metrics.maxBidiReorderingDepth == 1, "Unexpected reordering depth %u.\n", metrics.maxBidiReorderingDepth);
    ok(metrics.lineCount == 1, "Unexpected line count %u.\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetFlowDirection(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    DWRITE_READING_DIRECTION reading;
    DWRITE_FLOW_DIRECTION flow;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flow = IDWriteTextFormat_GetFlowDirection(format);
    ok(flow == DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM, "got %d\n", flow);

    reading = IDWriteTextFormat_GetReadingDirection(format);
    ok(reading == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", reading);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT);
    ok(hr == S_OK || broken(hr == E_INVALIDARG) /* vista,win7 */, "got 0x%08x\n", hr);
    if (hr == S_OK) {
        hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout_Release(layout);

        hr = IDWriteTextFormat_SetReadingDirection(format, DWRITE_READING_DIRECTION_TOP_TO_BOTTOM);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextFormat_SetFlowDirection(format, DWRITE_FLOW_DIRECTION_TOP_TO_BOTTOM);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout_Release(layout);
    }
    else
        win_skip("DWRITE_FLOW_DIRECTION_LEFT_TO_RIGHT is not supported\n");

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static const struct drawcall_entry draweffect_seq[] = {
    { DRAW_GLYPHRUN|DRAW_EFFECT, {'a','e',0x0300,0}, {'e','n','-','u','s',0}, 2 },
    { DRAW_GLYPHRUN, {'d',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draweffect2_seq[] = {
    { DRAW_GLYPHRUN|DRAW_EFFECT, {'a','e',0}, {'e','n','-','u','s',0}, 2 },
    { DRAW_GLYPHRUN, {'c','d',0}, {'e','n','-','u','s',0}, 2 },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draweffect3_seq[] = {
    { DRAW_INLINE|DRAW_EFFECT },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry draweffect4_seq[] = {
    { DRAW_INLINE },
    { DRAW_LAST_KIND }
};

static void test_SetDrawingEffect(void)
{
    static const WCHAR strW[] = {'a','e',0x0300,'d',0}; /* accent grave */
    static const WCHAR str2W[] = {'a','e','c','d',0};
    IDWriteInlineObject *sign;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    IUnknown *unk, *effect;
    DWRITE_TEXT_RANGE r;
    HRESULT hr;
    LONG ref;

    factory = create_factory();

    effect = create_test_effect();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* string with combining mark */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* set effect past the end of text */
    r.startPosition = 100;
    r.length = 10;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = r.length = 0;
    hr = IDWriteTextLayout_GetDrawingEffect(layout, 101, &unk, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 100 && r.length == 10, "got %u, %u\n", r.startPosition, r.length);
    IUnknown_Release(unk);

    r.startPosition = r.length = 0;
    unk = (void*)0xdeadbeef;
    hr = IDWriteTextLayout_GetDrawingEffect(layout, 1000, &unk, &r);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(r.startPosition == 110 && r.length == ~0u-110, "got %u, %u\n", r.startPosition, r.length);
    ok(unk == NULL, "got %p\n", unk);

    /* effect is applied to clusters, not individual text positions */
    r.startPosition = 0;
    r.length = 2;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect_seq, "effect draw test", TRUE);
    IDWriteTextLayout_Release(layout);

    /* simple string */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 2;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect2_seq, "effect draw test 2", FALSE);
    IDWriteTextLayout_Release(layout);

    /* Inline object - effect set for same range */
    hr = IDWriteFactory_CreateEllipsisTrimmingSign(factory, format, &sign);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 4, format, 500.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetInlineObject(layout, sign, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect3_seq, "effect draw test 3", FALSE);

    /* now set effect somewhere inside a range replaced by inline object */
    hr = IDWriteTextLayout_SetDrawingEffect(layout, NULL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 1;
    r.length = 1;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* no effect is reported in this case */
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect4_seq, "effect draw test 4", FALSE);

    r.startPosition = 0;
    r.length = 4;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, NULL, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    r.startPosition = 0;
    r.length = 1;
    hr = IDWriteTextLayout_SetDrawingEffect(layout, effect, r);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* first range position is all that matters for inline ranges */
    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, draweffect3_seq, "effect draw test 5", FALSE);

    IDWriteTextLayout_Release(layout);

    ref = IUnknown_Release(effect);
    ok(ref == 0, "Unexpected effect refcount %u\n", ref);
    IDWriteInlineObject_Release(sign);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static BOOL get_enus_string(IDWriteLocalizedStrings *strings, WCHAR *buff, UINT32 size)
{
    UINT32 index;
    BOOL exists = FALSE;
    HRESULT hr;

    hr = IDWriteLocalizedStrings_FindLocaleName(strings, enusW, &index, &exists);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    if (exists) {
        hr = IDWriteLocalizedStrings_GetString(strings, index, buff, size);
        ok(hr == S_OK, "got 0x%08x\n", hr);
    }
    else
        *buff = 0;

    return exists;
}

static void test_GetLineMetrics(void)
{
    static const WCHAR str3W[] = {'a','\r','b','\n','c','\n','\r','d','\r','\n',0};
    static const WCHAR strW[] = {'a','b','c','d',' ',0};
    static const WCHAR str2W[] = {'a','b','\r','c','d',0};
    static const WCHAR str4W[] = {'a','\r',0};
    static const WCHAR emptyW[] = {0};
    IDWriteFontCollection *syscollection;
    DWRITE_FONT_METRICS fontmetrics;
    DWRITE_LINE_METRICS metrics[6];
    UINT32 count, i, familycount;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFontFace *fontface;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE range;
    WCHAR nameW[256];
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 2048.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 5, format, 30000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 1, "got count %u\n", count);

    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(metrics[0].length == 5, "got %u\n", metrics[0].length);
    ok(metrics[0].trailingWhitespaceLength == 1, "got %u\n", metrics[0].trailingWhitespaceLength);

    ok(metrics[0].newlineLength == 0, "got %u\n", metrics[0].newlineLength);
    ok(metrics[0].isTrimmed == FALSE, "got %d\n", metrics[0].isTrimmed);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    /* Test line height and baseline calculation */
    hr = IDWriteFactory_GetSystemFontCollection(factory, &syscollection, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    familycount = IDWriteFontCollection_GetFontFamilyCount(syscollection);

    for (i = 0; i < familycount; i++) {
        IDWriteLocalizedStrings *names;
        IDWriteFontFamily *family;
        IDWriteFont *font;
        BOOL exists;

        format = NULL;
        layout = NULL;

        hr = IDWriteFontCollection_GetFontFamily(syscollection, i, &family);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFirstMatchingFont(family, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
            DWRITE_FONT_STYLE_NORMAL, &font);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFont_CreateFontFace(font, &fontface);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFamilyNames(family, &names);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        if (!(exists = get_enus_string(names, nameW, ARRAY_SIZE(nameW)))) {
            IDWriteLocalFontFileLoader *localloader;
            IDWriteFontFileLoader *loader;
            IDWriteFontFile *file;
            const void *key;
            UINT32 keysize;
            UINT32 count;

            count = 1;
            hr = IDWriteFontFace_GetFiles(fontface, &count, &file);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFile_GetLoader(file, &loader);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFileLoader_QueryInterface(loader, &IID_IDWriteLocalFontFileLoader, (void**)&localloader);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            IDWriteFontFileLoader_Release(loader);

            hr = IDWriteFontFile_GetReferenceKey(file, &key, &keysize);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteLocalFontFileLoader_GetFilePathFromKey(localloader, key, keysize, nameW, ARRAY_SIZE(nameW));
            ok(hr == S_OK, "got 0x%08x\n", hr);

            skip("Failed to get English family name, font file %s\n", wine_dbgstr_w(nameW));

            IDWriteLocalFontFileLoader_Release(localloader);
            IDWriteFontFile_Release(file);
        }

        IDWriteLocalizedStrings_Release(names);
        IDWriteFont_Release(font);

        if (!exists)
            goto cleanup;

        IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
        hr = IDWriteFactory_CreateTextFormat(factory, nameW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL, fontmetrics.designUnitsPerEm, enusW, &format);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, emptyW, 1, format, 30000.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        memset(metrics, 0, sizeof(metrics));
        count = 0;
        hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(count == 1, "got %u\n", count);

        ok(metrics[0].baseline == fontmetrics.ascent + fontmetrics.lineGap, "%s: got %.2f, expected %d, "
            "linegap %d\n", wine_dbgstr_w(nameW), metrics[0].baseline, fontmetrics.ascent + fontmetrics.lineGap,
            fontmetrics.lineGap);
        ok(metrics[0].height == fontmetrics.ascent + fontmetrics.descent + fontmetrics.lineGap,
            "%s: got %.2f, expected %d, linegap %d\n", wine_dbgstr_w(nameW), metrics[0].height,
            fontmetrics.ascent + fontmetrics.descent + fontmetrics.lineGap, fontmetrics.lineGap);

    cleanup:
        if (layout)
            IDWriteTextLayout_Release(layout);
        if (format)
            IDWriteTextFormat_Release(format);
        IDWriteFontFace_Release(fontface);
        IDWriteFontFamily_Release(family);
    }
    IDWriteFontCollection_Release(syscollection);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 2048.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fontface = get_fontface_from_format(format);
    ok(fontface != NULL, "got %p\n", fontface);

    /* force 2 lines */
    hr = IDWriteFactory_CreateTextLayout(factory, str2W, 5, format, 10000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(metrics, 0, sizeof(metrics));
    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    /* baseline is relative to a line, and is not accumulated */
    ok(metrics[0].baseline == metrics[1].baseline, "got %.2f, %.2f\n", metrics[0].baseline,
        metrics[1].baseline);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    /* line breaks */
    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, str3W, 10, format, 100.0, 300.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(metrics, 0xcc, sizeof(metrics));
    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 6, "got %u\n", count);

    ok(metrics[0].length == 2, "got %u\n", metrics[0].length);
    ok(metrics[1].length == 2, "got %u\n", metrics[1].length);
    ok(metrics[2].length == 2, "got %u\n", metrics[2].length);
    ok(metrics[3].length == 1, "got %u\n", metrics[3].length);
    ok(metrics[4].length == 3, "got %u\n", metrics[4].length);
    ok(metrics[5].length == 0, "got %u\n", metrics[5].length);

    ok(metrics[0].newlineLength == 1, "got %u\n", metrics[0].newlineLength);
    ok(metrics[1].newlineLength == 1, "got %u\n", metrics[1].newlineLength);
    ok(metrics[2].newlineLength == 1, "got %u\n", metrics[2].newlineLength);
    ok(metrics[3].newlineLength == 1, "got %u\n", metrics[3].newlineLength);
    ok(metrics[4].newlineLength == 2, "got %u\n", metrics[4].newlineLength);
    ok(metrics[5].newlineLength == 0, "got %u\n", metrics[5].newlineLength);

    ok(metrics[0].trailingWhitespaceLength == 1, "got %u\n", metrics[0].newlineLength);
    ok(metrics[1].trailingWhitespaceLength == 1, "got %u\n", metrics[1].newlineLength);
    ok(metrics[2].trailingWhitespaceLength == 1, "got %u\n", metrics[2].newlineLength);
    ok(metrics[3].trailingWhitespaceLength == 1, "got %u\n", metrics[3].newlineLength);
    ok(metrics[4].trailingWhitespaceLength == 2, "got %u\n", metrics[4].newlineLength);
    ok(metrics[5].trailingWhitespaceLength == 0, "got %u\n", metrics[5].newlineLength);

    IDWriteTextLayout_Release(layout);

    /* empty text layout */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 0, format, 100.0f, 300.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[0].length == 0, "got %u\n", metrics[0].length);
    ok(metrics[0].trailingWhitespaceLength == 0, "got %u\n", metrics[0].trailingWhitespaceLength);
    ok(metrics[0].newlineLength == 0, "got %u\n", metrics[0].newlineLength);
    ok(metrics[0].height > 0.0f, "got %f\n", metrics[0].height);
    ok(metrics[0].baseline > 0.0f, "got %f\n", metrics[0].baseline);
    ok(!metrics[0].isTrimmed, "got %d\n", metrics[0].isTrimmed);

    /* change font size at first position, see if metrics changed */
    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 80.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 1, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[1].height > metrics[0].height, "got %f\n", metrics[1].height);
    ok(metrics[1].baseline > metrics[0].baseline, "got %f\n", metrics[1].baseline);

    /* revert font size back to format value, set different size for position 1 */
    hr = IDWriteTextLayout_SetFontSize(layout, 12.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 80.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    memset(metrics + 1, 0, sizeof(*metrics));
    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 1, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);
    ok(metrics[1].height == metrics[0].height, "got %f\n", metrics[1].height);
    ok(metrics[1].baseline == metrics[0].baseline, "got %f\n", metrics[1].baseline);

    IDWriteTextLayout_Release(layout);

    /* text is "a\r" */
    hr = IDWriteFactory_CreateTextLayout(factory, str4W, 2, format, 100.0f, 300.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    memset(metrics, 0, sizeof(metrics));
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[0].length == 2, "got %u\n", metrics[0].length);
    ok(metrics[0].newlineLength == 1, "got %u\n", metrics[0].newlineLength);
    ok(metrics[0].height > 0.0f, "got %f\n", metrics[0].height);
    ok(metrics[0].baseline > 0.0f, "got %f\n", metrics[0].baseline);
    ok(metrics[1].length == 0, "got %u\n", metrics[1].length);
    ok(metrics[1].newlineLength == 0, "got %u\n", metrics[1].newlineLength);
    ok(metrics[1].height > 0.0f, "got %f\n", metrics[1].height);
    ok(metrics[1].baseline > 0.0f, "got %f\n", metrics[1].baseline);

    range.startPosition = 1;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 80.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 2, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[3].height > metrics[1].height, "got %f, old %f\n", metrics[3].height, metrics[1].height);
    ok(metrics[3].baseline > metrics[1].baseline, "got %f, old %f\n", metrics[3].baseline, metrics[1].baseline);

    /* revert to original format */
    hr = IDWriteTextLayout_SetFontSize(layout, 12.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 2, 2, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);
    ok(metrics[3].height == metrics[1].height, "got %f, old %f\n", metrics[3].height, metrics[1].height);
    ok(metrics[3].baseline == metrics[1].baseline, "got %f, old %f\n", metrics[3].baseline, metrics[1].baseline);

    /* Switch to uniform spacing */
    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_UNIFORM, 456.0f, 123.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);

    for (i = 0; i < count; i++) {
        ok(metrics[i].height == 456.0f, "%u: got line height %f\n", i, metrics[i].height);
        ok(metrics[i].baseline == 123.0f, "%u: got line baseline %f\n", i, metrics[i].baseline);
    }

    IDWriteTextLayout_Release(layout);

    /* Switch to proportional */
    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_PROPORTIONAL, 2.0f, 4.0f);
    if (hr == S_OK) {
        hr = IDWriteFactory_CreateTextLayout(factory, str4W, 1, format, 100.0f, 300.0f, &layout);
        ok(hr == S_OK, "Failed to create layout, hr %#x.\n", hr);

        hr = IDWriteTextLayout_GetLineMetrics(layout, metrics, ARRAY_SIZE(metrics), &count);
        ok(hr == S_OK, "Failed to get line metrics, hr %#x.\n", hr);
        ok(count == 1, "Unexpected line count %u\n", count);

        /* Back to default mode. */
        hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, 0.0f);
        ok(hr == S_OK, "Failed to set spacing method, hr %#x.\n", hr);

        hr = IDWriteTextLayout_GetLineMetrics(layout, metrics + 1, 1, &count);
        ok(hr == S_OK, "Failed to get line metrics, hr %#x.\n", hr);
        ok(count == 1, "Unexpected line count %u\n", count);

        /* Proportional spacing applies multipliers to default, content based spacing. */
        ok(metrics[0].height == 2.0f * metrics[1].height, "Unexpected line height %f.\n", metrics[0].height);
        ok(metrics[0].baseline == 4.0f * metrics[1].baseline, "Unexpected line baseline %f.\n", metrics[0].baseline);

        IDWriteTextLayout_Release(layout);
    }
    else
        win_skip("Proportional spacing is not supported.\n");

    IDWriteTextFormat_Release(format);
    IDWriteFontFace_Release(fontface);
    IDWriteFactory_Release(factory);
}

static void test_SetTextAlignment(void)
{
    static const WCHAR strW[] = {'a',0};

    static const WCHAR stringsW[][10] = {
        {'a',0},
        {0}
    };

    DWRITE_CLUSTER_METRICS clusters[10];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat1 *format1;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_TEXT_ALIGNMENT v;
    UINT32 count, i;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetTextAlignment(format);
    ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetTextAlignment(layout);
    ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetTextAlignment(format);
    ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

    v = IDWriteTextLayout_GetTextAlignment(layout);
    ok(v == DWRITE_TEXT_ALIGNMENT_TRAILING, "got %d\n", v);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat1, (void**)&format1);
    if (hr == S_OK) {
        hr = IDWriteTextFormat1_SetTextAlignment(format1, DWRITE_TEXT_ALIGNMENT_CENTER);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        v = IDWriteTextFormat_GetTextAlignment(format);
        ok(v == DWRITE_TEXT_ALIGNMENT_LEADING, "got %d\n", v);

        v = IDWriteTextLayout_GetTextAlignment(layout);
        ok(v == DWRITE_TEXT_ALIGNMENT_CENTER, "got %d\n", v);

        v = IDWriteTextFormat1_GetTextAlignment(format1);
        ok(v == DWRITE_TEXT_ALIGNMENT_CENTER, "got %d\n", v);

        IDWriteTextFormat1_Release(format1);
    }
    else
        win_skip("IDWriteTextFormat1 is not supported\n");

    IDWriteTextLayout_Release(layout);

    for (i = 0; i < ARRAY_SIZE(stringsW); i++) {
        FLOAT text_width;

        hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_LEADING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, 500.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        count = 0;
        hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, ARRAY_SIZE(clusters), &count);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        if (stringsW[i][0])
            ok(count > 0, "got %u\n", count);
        else
            ok(count == 0, "got %u\n", count);

        text_width = 0.0f;
        while (count)
            text_width += clusters[--count].width;

        /* maxwidth is 500, leading alignment */
        hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_LEADING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(metrics.left == 0.0f, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.layoutWidth == 500.0f, "got %.2f\n", metrics.layoutWidth);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

        /* maxwidth is 500, trailing alignment */
        hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(metrics.left == metrics.layoutWidth - metrics.width, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.layoutWidth == 500.0f, "got %.2f\n", metrics.layoutWidth);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
        IDWriteTextLayout_Release(layout);

        /* initially created with trailing alignment */
        hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_TRAILING);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, 500.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(metrics.left == metrics.layoutWidth - metrics.width, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.layoutWidth == 500.0f, "got %.2f\n", metrics.layoutWidth);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
        IDWriteTextLayout_Release(layout);

        if (stringsW[i][0]) {
            /* max width less than total run width, trailing alignment */
            hr = IDWriteTextFormat_SetWordWrapping(format, DWRITE_WORD_WRAPPING_NO_WRAP);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, clusters[0].width, 100.0f, &layout);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            ok(metrics.left == metrics.layoutWidth - metrics.width, "got %.2f\n", metrics.left);
            ok(metrics.width == text_width, "got %.2f\n", metrics.width);
            ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
            IDWriteTextLayout_Release(layout);
        }

        /* maxwidth is 500, centered */
        hr = IDWriteTextFormat_SetTextAlignment(format, DWRITE_TEXT_ALIGNMENT_CENTER);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, stringsW[i], lstrlenW(stringsW[i]), format, 500.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(metrics.left == (metrics.layoutWidth - metrics.width) / 2.0f, "got %.2f\n", metrics.left);
        ok(metrics.width == text_width, "got %.2f\n", metrics.width);
        ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

        IDWriteTextLayout_Release(layout);
    }

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetParagraphAlignment(void)
{
    static const WCHAR strW[] = {'a',0};
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_PARAGRAPH_ALIGNMENT v;
    DWRITE_LINE_METRICS lines[1];
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetParagraphAlignment(format);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetParagraphAlignment(layout);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", v);

    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_FAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_FAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetParagraphAlignment(format);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "got %d\n", v);

    v = IDWriteTextLayout_GetParagraphAlignment(layout);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_FAR, "got %d\n", v);

    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetParagraphAlignment(layout);
    ok(v == DWRITE_PARAGRAPH_ALIGNMENT_CENTER, "got %d\n", v);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, lines, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    /* maxheight is 100, near alignment */
    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    /* maxwidth is 100, far alignment */
    hr = IDWriteTextLayout_SetParagraphAlignment(layout, DWRITE_PARAGRAPH_ALIGNMENT_FAR);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.top == metrics.layoutHeight - metrics.height, "got %.2f\n", metrics.top);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    /* initially created with centered alignment */
    hr = IDWriteTextFormat_SetParagraphAlignment(format, DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.top == (metrics.layoutHeight - lines[0].height) / 2, "got %.2f\n", metrics.top);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);
    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_SetReadingDirection(void)
{
    static const WCHAR strW[] = {'a',0};
    DWRITE_CLUSTER_METRICS clusters[1];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_READING_DIRECTION v;
    DWRITE_LINE_METRICS lines[1];
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetReadingDirection(format);
    ok(v == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetReadingDirection(layout);
    ok(v == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", v);

    v = IDWriteTextFormat_GetReadingDirection(format);
    ok(v == DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, "got %d\n", v);

    hr = IDWriteTextLayout_SetReadingDirection(layout, DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, lines, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 1, &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(count == 1, "got %u\n", count);

    /* leading alignment, RTL */
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.left == metrics.layoutWidth - clusters[0].width, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f\n", metrics.width);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    /* trailing alignment, RTL */
    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_TRAILING);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.left == 0.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f\n", metrics.width);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    /* centered alignment, RTL */
    hr = IDWriteTextLayout_SetTextAlignment(layout, DWRITE_TEXT_ALIGNMENT_CENTER);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    ok(metrics.left == (metrics.layoutWidth - clusters[0].width) / 2.0, "got %.2f\n", metrics.left);
    ok(metrics.top == 0.0, "got %.2f\n", metrics.top);
    ok(metrics.width == clusters[0].width, "got %.2f\n", metrics.width);
    ok(metrics.height == lines[0].height, "got %.2f\n", metrics.height);
    ok(metrics.layoutWidth == 500.0, "got %.2f\n", metrics.layoutWidth);
    ok(metrics.layoutHeight == 100.0, "got %.2f\n", metrics.layoutHeight);
    ok(metrics.lineCount == 1, "got %d\n", metrics.lineCount);

    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static inline FLOAT get_scaled_font_metric(UINT32 metric, FLOAT emSize, const DWRITE_FONT_METRICS *metrics)
{
    return (FLOAT)metric * emSize / (FLOAT)metrics->designUnitsPerEm;
}

static FLOAT snap_coord(const DWRITE_MATRIX *m, FLOAT ppdip, FLOAT coord)
{
    FLOAT vec[2], det, vec2[2];
    BOOL transform;

    /* has to be a diagonal matrix */
    if ((ppdip <= 0.0) ||
        (m->m11 * m->m22 != 0.0 && (m->m12 != 0.0 || m->m21 != 0.0)) ||
        (m->m12 * m->m21 != 0.0 && (m->m11 != 0.0 || m->m22 != 0.0)))
        return coord;

    det = m->m11 * m->m22 - m->m12 * m->m21;
    transform = fabsf(det) > 1e-10;

    if (transform) {
        /* apply transform */
        vec[0] = 0.0;
        vec[1] = coord * ppdip;

        vec2[0] = m->m11 * vec[0] + m->m21 * vec[1] + m->dx;
        vec2[1] = m->m12 * vec[0] + m->m22 * vec[1] + m->dy;

        /* snap */
        vec2[0] = floorf(vec2[0] + 0.5f);
        vec2[1] = floorf(vec2[1] + 0.5f);

        /* apply inverted transform */
        vec[1] = (-m->m12 * vec2[0] + m->m11 * vec2[1] - (m->m11 * m->dy - m->m12 * m->dx)) / det;
        vec[1] /= ppdip;
    }
    else
        vec[1] = floorf(coord * ppdip + 0.5f) / ppdip;
    return vec[1];
}

static inline BOOL float_eq(FLOAT left, FLOAT right)
{
    int x = *(int *)&left;
    int y = *(int *)&right;

    if (x < 0)
        x = INT_MIN - x;
    if (y < 0)
        y = INT_MIN - y;

    return abs(x - y) <= 16;
}

struct snapping_test {
    DWRITE_MATRIX m;
    FLOAT ppdip;
};

static struct snapping_test snapping_tests[] = {
    { {  0.0,  1.0,  2.0,  0.0, 0.2, 0.3 },   1.0 },
    { {  0.0,  1.0,  2.0,  0.0, 0.0, 0.0 },   1.0 },
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 },   1.0 }, /* identity transform */
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 },   0.9 },
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 },  -1.0 },
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 },   0.0 },
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.3 },   1.0 }, /* simple Y shift */
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 },  10.0 }, /* identity, 10 ppdip */
    { {  1.0,  0.0,  0.0, 10.0, 0.0, 0.0 },  10.0 },
    { {  0.0,  1.0,  1.0,  0.0, 0.2, 0.6 },   1.0 },
    { {  0.0,  2.0,  2.0,  0.0, 0.2, 0.6 },   1.0 },
    { {  0.0,  0.5, -0.5,  0.0, 0.2, 0.6 },   1.0 },
    { {  1.0,  2.0,  0.0,  1.0, 0.2, 0.6 },   1.0 },
    { {  1.0,  1.0,  0.0,  1.0, 0.2, 0.6 },   1.0 },
    { {  0.5,  0.5, -0.5,  0.5, 0.2, 0.6 },   1.0 }, /*  45 degrees rotation */
    { {  0.5,  0.5, -0.5,  0.5, 0.0, 0.0 }, 100.0 }, /*  45 degrees rotation */
    { {  1.0,  0.0,  0.0,  1.0, 0.0, 0.0 }, 100.0 },
    { {  0.0,  1.0, -1.0,  0.0, 0.2, 0.6 },   1.0 }, /*  90 degrees rotation */
    { { -1.0,  0.0,  0.0, -1.0, 0.2, 0.6 },   1.0 }, /* 180 degrees rotation */
    { {  0.0, -1.0,  1.0,  0.0, 0.2, 0.6 },   1.0 }, /* 270 degrees rotation */
    { {  1.0,  0.0,  0.0,  1.0,-0.1, 0.2 },   1.0 },
    { {  0.0,  1.0, -1.0,  0.0,-0.2,-0.3 },   1.0 }, /*  90 degrees rotation */
    { { -1.0,  0.0,  0.0, -1.0,-0.3,-1.6 },   1.0 }, /* 180 degrees rotation */
    { {  0.0, -1.0,  1.0,  0.0,-0.7, 0.6 },  10.0 }, /* 270 degrees rotation */
    { {  0.0,  2.0,  1.0,  0.0, 0.2, 0.6 },   1.0 },
    { {  0.0,  0.0,  1.0,  0.0, 0.0, 0.0 },   1.0 },
    { {  3.0,  0.0,  0.0,  5.0, 0.2,-0.3 },  10.0 },
    { {  0.0, -3.0,  5.0,  0.0,-0.1, 0.7 },  10.0 },
};

static DWRITE_MATRIX compattransforms[] = {
    { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 },
    { 1.0, 0.0, 0.0, 1.0, 0.2, 0.3 },
    { 2.0, 0.0, 0.0, 2.0, 0.2, 0.3 },
    { 2.0, 1.0, 2.0, 2.0, 0.2, 0.3 },
};

static void test_pixelsnapping(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextLayout *layout, *layout2;
    struct renderer_context ctxt;
    DWRITE_FONT_METRICS metrics;
    IDWriteTextFormat *format;
    IDWriteFontFace *fontface;
    IDWriteFactory *factory;
    FLOAT baseline, originX;
    HRESULT hr;
    int i, j;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fontface = get_fontface_from_format(format);
    IDWriteFontFace_GetMetrics(fontface, &metrics);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 500.0, 100.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* disabled snapping */
    ctxt.snapping_disabled = TRUE;
    ctxt.gdicompat = FALSE;
    ctxt.use_gdi_natural = FALSE;
    ctxt.ppdip = 1.0f;
    memset(&ctxt.m, 0, sizeof(ctxt.m));
    ctxt.m.m11 = ctxt.m.m22 = 1.0;
    originX = 0.1;

    hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, originX, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    baseline = get_scaled_font_metric(metrics.ascent, 12.0, &metrics);
    ok(ctxt.originX == originX, "got %f, originX %f\n", ctxt.originX, originX);
    ok(ctxt.originY == baseline, "got %f, baseline %f\n", ctxt.originY, baseline);
    ok(floor(baseline) != baseline, "got %f\n", baseline);

    ctxt.snapping_disabled = FALSE;

    for (i = 0; i < ARRAY_SIZE(snapping_tests); i++) {
        struct snapping_test *ptr = &snapping_tests[i];
        FLOAT expectedY;

        ctxt.m = ptr->m;
        ctxt.ppdip = ptr->ppdip;
        ctxt.originX = 678.9;
        ctxt.originY = 678.9;

        expectedY = snap_coord(&ctxt.m, ctxt.ppdip, baseline);
        hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, originX, 0.0);
        ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);
        ok(ctxt.originX == originX, "%d: got %f, originX %f\n", i, ctxt.originX, originX);
        ok(float_eq(ctxt.originY, expectedY), "%d: got %f, expected %f, baseline %f\n",
            i, ctxt.originY, expectedY, baseline);

        /* gdicompat layout transform doesn't affect snapping */
        for (j = 0; j < ARRAY_SIZE(compattransforms); j++) {
            hr = IDWriteFactory_CreateGdiCompatibleTextLayout(factory, strW, 1, format, 500.0, 100.0,
                1.0, &compattransforms[j], FALSE, &layout2);
            ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);

            expectedY = snap_coord(&ctxt.m, ctxt.ppdip, baseline);
            hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, originX, 0.0);
            ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);
            ok(ctxt.originX == originX, "%d: got %f, originX %f\n", i, ctxt.originX, originX);
            ok(float_eq(ctxt.originY, expectedY), "%d: got %f, expected %f, baseline %f\n",
                i, ctxt.originY, expectedY, baseline);

            IDWriteTextLayout_Release(layout2);
        }
    }

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFontFace_Release(fontface);
    IDWriteFactory_Release(factory);
}

static void test_SetWordWrapping(void)
{
    static const WCHAR strW[] = {'a',' ','s','o','m','e',' ','t','e','x','t',' ','a','n','d',
        ' ','a',' ','b','i','t',' ','m','o','r','e','\n','b'};
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory *factory;
    DWRITE_WORD_WRAPPING v;
    UINT32 count;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 12.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetWordWrapping(format);
    ok(v == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", v);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, ARRAY_SIZE(strW), format, 10.0f, 100.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextLayout_GetWordWrapping(layout);
    ok(v == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", v);

    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    v = IDWriteTextFormat_GetWordWrapping(format);
    ok(v == DWRITE_WORD_WRAPPING_WRAP, "got %d\n", v);

    /* disable wrapping, text has explicit newline */
    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_NO_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count == 2, "got %u\n", count);

    hr = IDWriteTextLayout_SetWordWrapping(layout, DWRITE_WORD_WRAPPING_WRAP);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetLineMetrics(layout, NULL, 0, &count);
    ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
    ok(count > 2, "got %u\n", count);

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

/* Collection dedicated to fallback testing */

static const WCHAR g_blahfontW[] = {'B','l','a','h',0};
static HRESULT WINAPI fontcollection_QI(IDWriteFontCollection *iface, REFIID riid, void **obj)
{
    if (IsEqualIID(riid, &IID_IDWriteFontCollection) || IsEqualIID(riid, &IID_IUnknown)) {
        *obj = iface;
        IDWriteFontCollection_AddRef(iface);
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI fontcollection_AddRef(IDWriteFontCollection *iface)
{
    return 2;
}

static ULONG WINAPI fontcollection_Release(IDWriteFontCollection *iface)
{
    return 1;
}

static UINT32 WINAPI fontcollection_GetFontFamilyCount(IDWriteFontCollection *iface)
{
    ok(0, "unexpected call\n");
    return 0;
}

static HRESULT WINAPI fontcollection_GetFontFamily(IDWriteFontCollection *iface, UINT32 index, IDWriteFontFamily **family)
{
    if (index == 123456) {
        IDWriteFactory *factory = create_factory();
        IDWriteFontCollection *syscollection;
        BOOL exists;
        HRESULT hr;

        hr = IDWriteFactory_GetSystemFontCollection(factory, &syscollection, FALSE);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontCollection_FindFamilyName(syscollection, tahomaW, &index, &exists);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontCollection_GetFontFamily(syscollection, index, family);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        IDWriteFontCollection_Release(syscollection);
        IDWriteFactory_Release(factory);
        return S_OK;
    }
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI fontcollection_FindFamilyName(IDWriteFontCollection *iface, WCHAR const *name, UINT32 *index, BOOL *exists)
{
    if (!lstrcmpW(name, g_blahfontW)) {
        *index = 123456;
        *exists = TRUE;
        return S_OK;
    }
    ok(0, "unexpected call, name %s\n", wine_dbgstr_w(name));
    return E_NOTIMPL;
}

static HRESULT WINAPI fontcollection_GetFontFromFontFace(IDWriteFontCollection *iface, IDWriteFontFace *face, IDWriteFont **font)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static const IDWriteFontCollectionVtbl fallbackcollectionvtbl = {
    fontcollection_QI,
    fontcollection_AddRef,
    fontcollection_Release,
    fontcollection_GetFontFamilyCount,
    fontcollection_GetFontFamily,
    fontcollection_FindFamilyName,
    fontcollection_GetFontFromFontFace
};

static IDWriteFontCollection fallbackcollection = { &fallbackcollectionvtbl };

static void test_MapCharacters(void)
{
    static const WCHAR strW[] = {'a','b','c',0};
    static const WCHAR str2W[] = {'a',0x3058,'b',0};
    IDWriteLocalizedStrings *strings;
    IDWriteFontFallback *fallback;
    IDWriteFactory2 *factory2;
    IDWriteFactory *factory;
    UINT32 mappedlength;
    IDWriteFont *font;
    WCHAR buffW[50];
    BOOL exists;
    FLOAT scale;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_QueryInterface(factory, &IID_IDWriteFactory2, (void**)&factory2);
    IDWriteFactory_Release(factory);
    if (hr != S_OK) {
        win_skip("MapCharacters() is not supported\n");
        return;
    }

    fallback = NULL;
    hr = IDWriteFactory2_GetSystemFontFallback(factory2, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback != NULL, "got %p\n", fallback);

    mappedlength = 1;
    scale = 0.0f;
    font = (void*)0xdeadbeef;
    hr = IDWriteFontFallback_MapCharacters(fallback, NULL, 0, 0, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
    ok(mappedlength == 0, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font == NULL, "got %p\n", font);

    /* zero length source */
    g_source = strW;
    mappedlength = 1;
    scale = 0.0f;
    font = (void*)0xdeadbeef;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 0, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 0, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font == NULL, "got %p\n", font);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    IDWriteFont_Release(font);
}
    /* same latin text, full length */
    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 3, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 3, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    IDWriteFont_Release(font);
}
    /* string 'a\x3058b' */
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 3, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    IDWriteFont_Release(font);
}
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 1, 2, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
}
    ok(scale == 1.0f, "got %f\n", scale);
todo_wine
    ok(font != NULL, "got %p\n", font);
if (font) {
    /* font returned for Hiragana character, check if it supports Latin too */
    exists = FALSE;
    hr = IDWriteFont_HasCharacter(font, 'b', &exists);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(exists, "got %d\n", exists);

    IDWriteFont_Release(font);
}
    /* Try with explicit collection, Tahoma will be forced. */
    /* 1. Latin part */
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 3, &fallbackcollection, g_blahfontW, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);

    exists = FALSE;
    hr = IDWriteFont_GetInformationalStrings(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, &strings, &exists);
    ok(hr == S_OK && exists, "got 0x%08x, exists %d\n", hr, exists);
    hr = IDWriteLocalizedStrings_GetString(strings, 0, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(!lstrcmpW(buffW, tahomaW), "%s\n", wine_dbgstr_w(buffW));
    IDWriteLocalizedStrings_Release(strings);
    IDWriteFont_Release(font);

    /* 2. Hiragana character, force Tahoma font does not support Japanese */
    g_source = str2W;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 1, 1, &fallbackcollection, g_blahfontW, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);

    exists = FALSE;
    hr = IDWriteFont_GetInformationalStrings(font, DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, &strings, &exists);
    ok(hr == S_OK && exists, "got 0x%08x, exists %d\n", hr, exists);
    hr = IDWriteLocalizedStrings_GetString(strings, 0, buffW, ARRAY_SIZE(buffW));
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(lstrcmpW(buffW, tahomaW), "%s\n", wine_dbgstr_w(buffW));
    IDWriteLocalizedStrings_Release(strings);
    IDWriteFont_Release(font);

    IDWriteFontFallback_Release(fallback);
    IDWriteFactory2_Release(factory2);
}

static void test_FontFallbackBuilder(void)
{
    static const WCHAR localeW[] = {'l','o','c','a','l','e',0};
    static const WCHAR strW[] = {'A',0};
    IDWriteFontFallback *fallback, *fallback2;
    IDWriteFontFallbackBuilder *builder;
    IDWriteFontFallback1 *fallback1;
    DWRITE_UNICODE_RANGE range;
    IDWriteFactory2 *factory2;
    IDWriteFactory *factory;
    const WCHAR *familyW;
    UINT32 mappedlength;
    IDWriteFont *font;
    FLOAT scale;
    HRESULT hr;
    ULONG ref;

    factory = create_factory();

    hr = IDWriteFactory_QueryInterface(factory, &IID_IDWriteFactory2, (void**)&factory2);
    IDWriteFactory_Release(factory);

    if (hr != S_OK) {
        win_skip("IDWriteFontFallbackBuilder is not supported\n");
        return;
    }

    EXPECT_REF(factory2, 1);
    hr = IDWriteFactory2_CreateFontFallbackBuilder(factory2, &builder);
    EXPECT_REF(factory2, 2);

    fallback = NULL;
    EXPECT_REF(factory2, 2);
    EXPECT_REF(builder, 1);
    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(factory2, 3);
    EXPECT_REF(fallback, 1);
    EXPECT_REF(builder, 1);

    IDWriteFontFallback_AddRef(fallback);
    EXPECT_REF(builder, 1);
    EXPECT_REF(fallback, 2);
    EXPECT_REF(factory2, 3);
    IDWriteFontFallback_Release(fallback);

    /* New instance is created every time, even if mappings have not changed. */
    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback2);
    ok(hr == S_OK, "Failed to create fallback object, hr %#x.\n", hr);
    ok(fallback != fallback2, "Unexpected fallback instance.\n");
    IDWriteFontFallback_Release(fallback2);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, NULL, 0, NULL, 0, NULL, NULL, NULL, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    range.first = 'A';
    range.last = 'B';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 0, NULL, 0, NULL, NULL, NULL, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 0, NULL, 0, NULL, NULL, NULL, 1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 0, &familyW, 1, NULL, NULL, NULL, 1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, NULL, 0, &familyW, 1, NULL, NULL, NULL, 1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    /* negative scaling factor */
    range.first = range.last = 0;
    familyW = g_blahfontW;
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, -1.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 0.0f);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);

    /* empty range */
    range.first = range.last = 0;
    familyW = g_blahfontW;
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 1.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.first = range.last = 0;
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 2.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.first = range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 3.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.first = 'B';
    range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, NULL, NULL, NULL, 4.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    IDWriteFontFallback_Release(fallback);

    if (0) /* crashes on native */
        hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, NULL);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* fallback font missing from system collection */
    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = (void*)0xdeadbeef;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 1.0f, "got %f\n", scale);
    ok(font == NULL, "got %p\n", font);
}
    IDWriteFontFallback_Release(fallback);

    /* remap with custom collection */
    range.first = range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, &fallbackcollection, NULL, NULL, 5.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 5.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);
}
    if (font)
        IDWriteFont_Release(font);

    IDWriteFontFallback_Release(fallback);

    range.first = 'B';
    range.last = 'A';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, &fallbackcollection, NULL, NULL, 6.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 5.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);
}
    if (font)
        IDWriteFont_Release(font);

    IDWriteFontFallback_Release(fallback);

    /* explicit locale */
    range.first = 'A';
    range.last = 'B';
    hr = IDWriteFontFallbackBuilder_AddMapping(builder, &range, 1, &familyW, 1, &fallbackcollection, localeW, NULL, 6.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFontFallbackBuilder_CreateFontFallback(builder, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    g_source = strW;
    mappedlength = 0;
    scale = 0.0f;
    font = NULL;
    hr = IDWriteFontFallback_MapCharacters(fallback, &analysissource, 0, 1, NULL, NULL, DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, &mappedlength, &font, &scale);
todo_wine {
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(mappedlength == 1, "got %u\n", mappedlength);
    ok(scale == 5.0f, "got %f\n", scale);
    ok(font != NULL, "got %p\n", font);
}
    if (font)
        IDWriteFont_Release(font);

    if (SUCCEEDED(IDWriteFontFallback_QueryInterface(fallback, &IID_IDWriteFontFallback1, (void **)&fallback1)))
    {
        IDWriteFontFallback1_Release(fallback1);
    }
    else
        win_skip("IDWriteFontFallback1 is not supported.\n");

    IDWriteFontFallback_Release(fallback);

    IDWriteFontFallbackBuilder_Release(builder);
    ref = IDWriteFactory2_Release(factory2);
    ok(ref == 0, "Factory is not released, ref %u.\n", ref);
}

static void test_fallback(void)
{
    static const WCHAR strW[] = {'a','b','c','d',0};
    IDWriteFontFallback *fallback, *fallback2;
    IDWriteFontFallback1 *fallback1;
    DWRITE_CLUSTER_METRICS clusters[4];
    DWRITE_TEXT_METRICS metrics;
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat1 *format1;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    IDWriteFactory2 *factory2;
    IDWriteFactory *factory;
    UINT32 count, i;
    FLOAT width;
    HRESULT hr;

    factory = create_factory();

    /* Font does not exist in system collection. */
    hr = IDWriteFactory_CreateTextFormat(factory, g_blahfontW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %#x.\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
todo_wine {
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(count == 4, "Unexpected count %u.\n", count);
}
    for (i = 0, width = 0.0; i < count; i++)
        width += clusters[i].width;

    memset(&metrics, 0xcc, sizeof(metrics));
    hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
    ok(hr == S_OK, "Failed to get layout metrics, hr %#x.\n", hr);
todo_wine {
    ok(metrics.width > 0.0 && metrics.width == width, "Unexpected width %.2f, expected %.2f.\n", metrics.width, width);
    ok(metrics.height > 0.0, "Unexpected height %.2f.\n", metrics.height);
    ok(metrics.lineCount == 1, "Unexpected line count %u.\n", metrics.lineCount);
}
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    /* Existing font. */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    IDWriteTextLayout_Release(layout);

    if (hr != S_OK) {
        win_skip("GetFontFallback() is not supported.\n");
        IDWriteFactory_Release(factory);
        return;
    }

    if (0) /* crashes on native */
        hr = IDWriteTextLayout2_GetFontFallback(layout2, NULL);

    fallback = (void*)0xdeadbeef;
    hr = IDWriteTextLayout2_GetFontFallback(layout2, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback == NULL, "got %p\n", fallback);

    hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat1, (void**)&format1);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback = (void*)0xdeadbeef;
    hr = IDWriteTextFormat1_GetFontFallback(format1, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback == NULL, "got %p\n", fallback);

    hr = IDWriteFactory_QueryInterface(factory, &IID_IDWriteFactory2, (void**)&factory2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback = NULL;
    hr = IDWriteFactory2_GetSystemFontFallback(factory2, &fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback != NULL, "got %p\n", fallback);

    hr = IDWriteTextFormat1_SetFontFallback(format1, fallback);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback2 = (void*)0xdeadbeef;
    hr = IDWriteTextLayout2_GetFontFallback(layout2, &fallback2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback2 == fallback, "got %p\n", fallback2);

    hr = IDWriteTextLayout2_SetFontFallback(layout2, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    fallback2 = (void*)0xdeadbeef;
    hr = IDWriteTextFormat1_GetFontFallback(format1, &fallback2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(fallback2 == NULL, "got %p\n", fallback2);

    if (SUCCEEDED(IDWriteFontFallback_QueryInterface(fallback, &IID_IDWriteFontFallback1, (void **)&fallback1)))
    {
        IDWriteFontFallback1_Release(fallback1);
    }
    else
        win_skip("IDWriteFontFallback1 is not supported.\n");

    IDWriteFontFallback_Release(fallback);
    IDWriteTextFormat1_Release(format1);
    IDWriteTextLayout2_Release(layout2);
    IDWriteFactory_Release(factory);
}

static void test_SetTypography(void)
{
    static const WCHAR strW[] = {'a','f','i','b',0};
    IDWriteTypography *typography, *typography2;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextFormat_Release(format);

    hr = IDWriteFactory_CreateTypography(factory, &typography);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    EXPECT_REF(typography, 1);
    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetTypography(layout, typography, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(typography, 2);

    hr = IDWriteTextLayout_GetTypography(layout, 0, &typography2, NULL);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(typography2 == typography, "got %p, expected %p\n", typography2, typography);
    IDWriteTypography_Release(typography2);
    IDWriteTypography_Release(typography);

    hr = IDWriteFactory_CreateTypography(factory, &typography2);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetTypography(layout, typography2, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    EXPECT_REF(typography2, 2);
    IDWriteTypography_Release(typography2);

    hr = IDWriteTextLayout_GetTypography(layout, 0, &typography, &range);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok(range.length == 1, "got %u\n", range.length);

    IDWriteTypography_Release(typography);

    IDWriteTextLayout_Release(layout);
    IDWriteFactory_Release(factory);
}

static void test_SetLastLineWrapping(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat1 *format1;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;
    BOOL ret;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat1, (void**)&format1);
    IDWriteTextFormat_Release(format);
    if (hr != S_OK) {
        win_skip("SetLastLineWrapping() is not supported\n");
        IDWriteFactory_Release(factory);
        return;
    }

    ret = IDWriteTextFormat1_GetLastLineWrapping(format1);
    ok(ret, "got %d\n", ret);

    hr = IDWriteTextFormat1_SetLastLineWrapping(format1, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, (IDWriteTextFormat*)format1, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);

    ret = IDWriteTextLayout2_GetLastLineWrapping(layout2);
    ok(!ret, "got %d\n", ret);

    hr = IDWriteTextLayout2_SetLastLineWrapping(layout2, TRUE);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    IDWriteTextLayout2_Release(layout2);
    IDWriteTextFormat1_Release(format1);
    IDWriteFactory_Release(factory);
}

static void test_SetOpticalAlignment(void)
{
    static const WCHAR strW[] = {'a',0};
    DWRITE_OPTICAL_ALIGNMENT alignment;
    IDWriteTextLayout2 *layout2;
    IDWriteTextFormat1 *format1;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat1, (void**)&format1);
    IDWriteTextFormat_Release(format);
    if (hr != S_OK) {
        win_skip("SetOpticalAlignment() is not supported\n");
        IDWriteFactory_Release(factory);
        return;
    }

    alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, (IDWriteTextFormat*)format1, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout2, (void**)&layout2);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat1_Release(format1);

    alignment = IDWriteTextLayout2_GetOpticalAlignment(layout2);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    hr = IDWriteTextLayout2_QueryInterface(layout2, &IID_IDWriteTextFormat1, (void**)&format1);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    hr = IDWriteTextLayout2_SetOpticalAlignment(layout2, DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout2_SetOpticalAlignment(layout2, DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    alignment = IDWriteTextFormat1_GetOpticalAlignment(format1);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS, "got %d\n", alignment);

    hr = IDWriteTextFormat1_SetOpticalAlignment(format1, DWRITE_OPTICAL_ALIGNMENT_NONE);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat1_SetOpticalAlignment(format1, DWRITE_OPTICAL_ALIGNMENT_NO_SIDE_BEARINGS+1);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    alignment = IDWriteTextLayout2_GetOpticalAlignment(layout2);
    ok(alignment == DWRITE_OPTICAL_ALIGNMENT_NONE, "got %d\n", alignment);

    IDWriteTextLayout2_Release(layout2);
    IDWriteTextFormat1_Release(format1);
    IDWriteFactory_Release(factory);
}

static const struct drawcall_entry drawunderline_seq[] = {
    { DRAW_GLYPHRUN, {'a','e',0x0300,0}, {'e','n','-','u','s',0}, 2 }, /* reported runs can't mix different underline values */
    { DRAW_GLYPHRUN, {'d',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry drawunderline2_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_GLYPHRUN, {'e',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry drawunderline3_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','c','a',0}, 1 },
    { DRAW_GLYPHRUN, {'e',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','c','a',0} },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_LAST_KIND }
};

static const struct drawcall_entry drawunderline4_seq[] = {
    { DRAW_GLYPHRUN, {'a',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_GLYPHRUN, {'e',0}, {'e','n','-','u','s',0}, 1 },
    { DRAW_UNDERLINE, {0}, {'e','n','-','u','s',0} },
    { DRAW_STRIKETHROUGH },
    { DRAW_LAST_KIND }
};

static void test_SetUnderline(void)
{
    static const WCHAR encaW[] = {'e','n','-','C','A',0};
    static const WCHAR strW[] = {'a','e',0x0300,'d',0}; /* accent grave */
    IDWriteFontCollection *syscollection;
    DWRITE_CLUSTER_METRICS clusters[4];
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    DWRITE_TEXT_RANGE range;
    IDWriteFactory *factory;
    UINT32 count, i;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0, 1000.0, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, ARRAY_SIZE(clusters), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(count == 3, "got %u\n", count);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    count = 0;
    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, ARRAY_SIZE(clusters), &count);
    ok(hr == S_OK, "got 0x%08x\n", hr);
todo_wine
    ok(count == 3, "got %u\n", count);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0, 0.0);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline_seq, "draw underline test", TRUE);

    IDWriteTextLayout_Release(layout);

    /* 2 characters, same font, significantly different font size. Set underline for both, see how many
       underline drawing calls is there. */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 2, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetFontSize(layout, 100.0f, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline2_seq, "draw underline test 2", FALSE);

    /* now set different locale for second char, draw again */
    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetLocaleName(layout, encaW, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline3_seq, "draw underline test 2", FALSE);

    IDWriteTextLayout_Release(layout);

    /* 2 characters, same font properties, first with strikethrough, both underlined */
    hr = IDWriteFactory_CreateTextLayout(factory, strW, 2, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 1;
    hr = IDWriteTextLayout_SetStrikethrough(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    range.startPosition = 0;
    range.length = 2;
    hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    flush_sequence(sequences, RENDERER_ID);
    hr = IDWriteTextLayout_Draw(layout, NULL, &testrenderer, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    ok_sequence(sequences, RENDERER_ID, drawunderline4_seq, "draw underline test 4", FALSE);

    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);

    /* Test runHeight value with all available fonts */
    hr = IDWriteFactory_GetSystemFontCollection(factory, &syscollection, FALSE);
    ok(hr == S_OK, "got 0x%08x\n", hr);
    count = IDWriteFontCollection_GetFontFamilyCount(syscollection);

    for (i = 0; i < count; i++) {
        DWRITE_FONT_METRICS fontmetrics;
        IDWriteLocalizedStrings *names;
        struct renderer_context ctxt;
        IDWriteFontFamily *family;
        IDWriteFontFace *fontface;
        IDWriteFont *font;
        WCHAR nameW[256];
        BOOL exists;

        format = NULL;
        layout = NULL;

        hr = IDWriteFontCollection_GetFontFamily(syscollection, i, &family);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFirstMatchingFont(family, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
            DWRITE_FONT_STYLE_NORMAL, &font);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFont_CreateFontFace(font, &fontface);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFontFamily_GetFamilyNames(family, &names);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        if (!(exists = get_enus_string(names, nameW, ARRAY_SIZE(nameW)))) {
            IDWriteLocalFontFileLoader *localloader;
            IDWriteFontFileLoader *loader;
            IDWriteFontFile *file;
            const void *key;
            UINT32 keysize;
            UINT32 count;

            count = 1;
            hr = IDWriteFontFace_GetFiles(fontface, &count, &file);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFile_GetLoader(file, &loader);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteFontFileLoader_QueryInterface(loader, &IID_IDWriteLocalFontFileLoader, (void**)&localloader);
            ok(hr == S_OK, "got 0x%08x\n", hr);
            IDWriteFontFileLoader_Release(loader);

            hr = IDWriteFontFile_GetReferenceKey(file, &key, &keysize);
            ok(hr == S_OK, "got 0x%08x\n", hr);

            hr = IDWriteLocalFontFileLoader_GetFilePathFromKey(localloader, key, keysize, nameW, ARRAY_SIZE(nameW));
            ok(hr == S_OK, "got 0x%08x\n", hr);

            skip("Failed to get English family name, font file %s\n", wine_dbgstr_w(nameW));

            IDWriteLocalFontFileLoader_Release(localloader);
            IDWriteFontFile_Release(file);
        }

        IDWriteLocalizedStrings_Release(names);
        IDWriteFont_Release(font);

        if (!exists)
            goto cleanup;

        IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
        hr = IDWriteFactory_CreateTextFormat(factory, nameW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL, fontmetrics.designUnitsPerEm, enusW, &format);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteFactory_CreateTextLayout(factory, strW, 2, format, 30000.0f, 100.0f, &layout);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        range.startPosition = 0;
        range.length = 2;
        hr = IDWriteTextLayout_SetUnderline(layout, TRUE, range);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        memset(&ctxt, 0, sizeof(ctxt));
        ctxt.format = format;
        ctxt.familyW = nameW;
        hr = IDWriteTextLayout_Draw(layout, &ctxt, &testrenderer, 0.0f, 0.0f);
        ok(hr == S_OK, "got 0x%08x\n", hr);

    cleanup:
        if (layout)
            IDWriteTextLayout_Release(layout);
        if (format)
            IDWriteTextFormat_Release(format);
        IDWriteFontFace_Release(fontface);
        IDWriteFontFamily_Release(family);
    }
    IDWriteFontCollection_Release(syscollection);

    IDWriteFactory_Release(factory);
}

static void test_InvalidateLayout(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextLayout3 *layout3;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextLayout3, (void**)&layout3);
    if (hr == S_OK) {
        IDWriteTextFormat1 *format1;
        IDWriteTextFormat2 *format2;

        hr = IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextFormat2_Release(format2);

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Unexpected hr %#x.\n", hr);
        if (hr == S_OK) {
            ok(format != (IDWriteTextFormat *)format2, "Unexpected interface pointer.\n");
            IDWriteTextFormat2_Release(format2);
        }

        hr = IDWriteTextLayout_QueryInterface(layout, &IID_IDWriteTextFormat1, (void**)&format1);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextFormat1_QueryInterface(format1, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE), "Unexpected hr %#x.\n", hr);
        if (hr == S_OK)
            IDWriteTextFormat2_Release(format2);
        IDWriteTextFormat1_Release(format1);

        hr = IDWriteTextLayout3_QueryInterface(layout3, &IID_IDWriteTextFormat2, (void**)&format2);
        ok(hr == S_OK || broken(hr == E_NOINTERFACE), "got 0x%08x\n", hr);
        if (hr == S_OK)
            IDWriteTextFormat2_Release(format2);

        hr = IDWriteTextLayout3_InvalidateLayout(layout3);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        IDWriteTextLayout3_Release(layout3);
    }
    else
        win_skip("IDWriteTextLayout3::InvalidateLayout() is not supported.\n");

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_line_spacing(void)
{
    static const WCHAR strW[] = {'a',0};
    IDWriteTextFormat2 *format2;
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    HRESULT hr;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, -10.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_DEFAULT, -10.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextFormat_SetLineSpacing(format, DWRITE_LINE_SPACING_METHOD_PROPORTIONAL+1, 0.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, 0.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, 0.0f, -10.0f);
    ok(hr == S_OK, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_DEFAULT, -10.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_PROPORTIONAL+1, 0.0f, 0.0f);
    ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

    if (IDWriteTextFormat_QueryInterface(format, &IID_IDWriteTextFormat2, (void**)&format2) == S_OK) {
        DWRITE_LINE_SPACING spacing;

        hr = IDWriteTextFormat2_GetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        ok(spacing.method == DWRITE_LINE_SPACING_METHOD_DEFAULT, "got method %d\n", spacing.method);
        ok(spacing.height == 0.0f, "got %f\n", spacing.height);
        ok(spacing.baseline == -10.0f, "got %f\n", spacing.baseline);
        ok(spacing.leadingBefore == 0.0f, "got %f\n", spacing.leadingBefore);
        ok(spacing.fontLineGapUsage == DWRITE_FONT_LINE_GAP_USAGE_DEFAULT, "got %f\n", spacing.leadingBefore);

        spacing.leadingBefore = -1.0f;

        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        spacing.leadingBefore = 1.1f;

        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        spacing.leadingBefore = 1.0f;

        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        spacing.method = DWRITE_LINE_SPACING_METHOD_PROPORTIONAL + 1;
        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);

        spacing.method = DWRITE_LINE_SPACING_METHOD_PROPORTIONAL;
        spacing.fontLineGapUsage = DWRITE_FONT_LINE_GAP_USAGE_ENABLED + 1;
        hr = IDWriteTextFormat2_SetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);

        hr = IDWriteTextFormat2_GetLineSpacing(format2, &spacing);
        ok(hr == S_OK, "got 0x%08x\n", hr);
        ok(spacing.fontLineGapUsage == DWRITE_FONT_LINE_GAP_USAGE_ENABLED + 1, "got %d\n", spacing.fontLineGapUsage);

        IDWriteTextFormat2_Release(format2);
    }

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_GetOverhangMetrics(void)
{
    static const struct overhangs_test
    {
        FLOAT uniform_baseline;
        DWRITE_INLINE_OBJECT_METRICS metrics;
        DWRITE_OVERHANG_METRICS overhang_metrics;
        DWRITE_OVERHANG_METRICS expected;
    } overhangs_tests[] = {
        { 16.0f, { 10.0f, 50.0f, 20.0f }, { 1.0f, 2.0f, 3.0f, 4.0f }, { 1.0f, 6.0f, 3.0f, 0.0f } },
        { 15.0f, { 10.0f, 50.0f, 20.0f }, { 1.0f, 2.0f, 3.0f, 4.0f }, { 1.0f, 7.0f, 3.0f, -1.0f } },
        { 16.0f, { 10.0f, 50.0f, 20.0f }, { -1.0f, 0.0f, -3.0f, 4.0f }, { -1.0f, 4.0f, -3.0f, 0.0f } },
        { 15.0f, { 10.0f, 50.0f, 20.0f }, { -1.0f, 10.0f, 3.0f, -4.0f }, { -1.0f, 15.0f, 3.0f, -9.0f } },
    };
    static const WCHAR strW[] = {'A',0};
    IDWriteFactory *factory;
    IDWriteTextFormat *format;
    IDWriteTextLayout *layout;
    HRESULT hr;
    UINT32 i;

    factory = create_factory();

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL, 100.0f, enusW, &format);
    ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 1, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %x.\n", hr);

    for (i = 0; i < ARRAY_SIZE(overhangs_tests); i++) {
        const struct overhangs_test *test = &overhangs_tests[i];
        DWRITE_OVERHANG_METRICS overhang_metrics;
        DWRITE_TEXT_RANGE range = { 0, 1 };
        DWRITE_TEXT_METRICS metrics;
        struct test_inline_obj obj;

        test_inline_obj_init(&obj, &test->metrics, &test->overhang_metrics);

        hr = IDWriteTextLayout_SetLineSpacing(layout, DWRITE_LINE_SPACING_METHOD_UNIFORM, test->metrics.height * 2.0f,
                test->uniform_baseline);
        ok(hr == S_OK, "Failed to set line spacing, hr %#x.\n", hr);

        hr = IDWriteTextLayout_SetInlineObject(layout, NULL, range);
        ok(hr == S_OK, "Failed to reset inline object, hr %#x.\n", hr);

        hr = IDWriteTextLayout_SetInlineObject(layout, &obj.IDWriteInlineObject_iface, range);
        ok(hr == S_OK, "Failed to set inline object, hr %#x.\n", hr);

        hr = IDWriteTextLayout_GetMetrics(layout, &metrics);
        ok(hr == S_OK, "Failed to get layout metrics, hr %#x.\n", hr);

        ok(metrics.width == test->metrics.width, "%u: unexpected formatted width.\n", i);
        ok(metrics.height == test->metrics.height * 2.0f, "%u: unexpected formatted height.\n", i);

        hr = IDWriteTextLayout_SetMaxWidth(layout, metrics.width);
        hr = IDWriteTextLayout_SetMaxHeight(layout, test->metrics.height);

        hr = IDWriteTextLayout_GetOverhangMetrics(layout, &overhang_metrics);
        ok(hr == S_OK, "Failed to get overhang metrics, hr %#x.\n", hr);

        ok(!memcmp(&overhang_metrics, &test->expected, sizeof(overhang_metrics)),
                "%u: unexpected overhang metrics (%f, %f, %f, %f).\n", i, overhang_metrics.left, overhang_metrics.top,
                overhang_metrics.right, overhang_metrics.bottom);
    }

    IDWriteTextLayout_Release(layout);
    IDWriteTextFormat_Release(format);
    IDWriteFactory_Release(factory);
}

static void test_tab_stops(void)
{
    static const WCHAR strW[] = {'\t','a','\t','b'};
    DWRITE_CLUSTER_METRICS clusters[4];
    IDWriteTextLayout *layout;
    IDWriteTextFormat *format;
    IDWriteFactory *factory;
    DWRITE_TEXT_RANGE range;
    FLOAT tabstop, size;
    ULONG count;
    HRESULT hr;

    factory = create_factory();

    /* Default tab stop value. */
    for (size = 1.0f; size < 25.0f; size += 5.0f)
    {
        hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL,
                DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, size, enusW, &format);
        ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

        tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
        ok(tabstop == 4.0f * size, "Unexpected tab stop %f.\n", tabstop);

        IDWriteTextFormat_Release(format);
    }

    hr = IDWriteFactory_CreateTextFormat(factory, tahomaW, NULL, DWRITE_FONT_WEIGHT_NORMAL,
            DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 10.0f, enusW, &format);
    ok(hr == S_OK, "Failed to create text format, hr %#x.\n", hr);

    hr = IDWriteTextFormat_SetIncrementalTabStop(format, 0.0f);
    ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);

    hr = IDWriteTextFormat_SetIncrementalTabStop(format, -10.0f);
    ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);

    tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
    ok(tabstop == 40.0f, "Unexpected tab stop %f.\n", tabstop);

    hr = IDWriteTextFormat_SetIncrementalTabStop(format, 100.0f);
    ok(hr == S_OK, "Failed to set tab stop value, hr %#x.\n", hr);

    tabstop = IDWriteTextFormat_GetIncrementalTabStop(format);
    ok(tabstop == 100.0f, "Unexpected tab stop %f.\n", tabstop);

    hr = IDWriteFactory_CreateTextLayout(factory, strW, 4, format, 1000.0f, 1000.0f, &layout);
    ok(hr == S_OK, "Failed to create text layout, hr %x.\n", hr);

    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(clusters[0].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[1].isWhitespace, "Unexpected isWhitespace.\n");
    ok(clusters[2].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[3].isWhitespace, "Unexpected isWhitespace.\n");
todo_wine {
    ok(clusters[0].width == tabstop, "Unexpected tab width.\n");
    ok(clusters[1].width + clusters[2].width == tabstop, "Unexpected tab width.\n");
}
    range.startPosition = 0;
    range.length = ~0u;
    hr = IDWriteTextLayout_SetFontSize(layout, 20.0f, range);
    ok(hr == S_OK, "Failed to set font size, hr %#x.\n", hr);

    tabstop = IDWriteTextLayout_GetIncrementalTabStop(layout);
    ok(tabstop == 100.0f, "Unexpected tab stop %f.\n", tabstop);

    hr = IDWriteTextLayout_GetClusterMetrics(layout, clusters, 4, &count);
    ok(hr == S_OK, "Failed to get cluster metrics, hr %#x.\n", hr);
    ok(clusters[0].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[1].isWhitespace, "Unexpected isWhitespace.\n");
    ok(clusters[2].isWhitespace, "Unexpected isWhitespace.\n");
    ok(!clusters[3].isWhitespace, "Unexpected isWhitespace.\n");
todo_wine {
    ok(clusters[0].width == tabstop, "Unexpected tab width.\n");
    ok(clusters[1].width + clusters[2].width == tabstop, "Unexpected tab width.\n");
}
    IDWriteTextLayout_Release(layout);

    IDWriteTextFormat_Release(format);

    IDWriteFactory_Release(factory);
}

START_TEST(layout)
{
    IDWriteFactory *factory;

    if (!(factory = create_factory())) {
        win_skip("failed to create factory\n");
        return;
    }

    init_call_sequences(sequences, NUM_CALL_SEQUENCES);
    init_call_sequences(expected_seq, 1);

    test_CreateTextLayout();
    test_CreateGdiCompatibleTextLayout();
    test_CreateTextFormat();
    test_GetLocaleName();
    test_CreateEllipsisTrimmingSign();
    test_fontweight();
    test_SetInlineObject();
    test_Draw();
    test_typography();
    test_GetClusterMetrics();
    test_SetLocaleName();
    test_SetPairKerning();
    test_SetVerticalGlyphOrientation();
    test_fallback();
    test_DetermineMinWidth();
    test_SetFontSize();
    test_SetFontFamilyName();
    test_SetFontStyle();
    test_SetFontStretch();
    test_SetStrikethrough();
    test_GetMetrics();
    test_SetFlowDirection();
    test_SetDrawingEffect();
    test_GetLineMetrics();
    test_SetTextAlignment();
    test_SetParagraphAlignment();
    test_SetReadingDirection();
    test_pixelsnapping();
    test_SetWordWrapping();
    test_MapCharacters();
    test_FontFallbackBuilder();
    test_SetTypography();
    test_SetLastLineWrapping();
    test_SetOpticalAlignment();
    test_SetUnderline();
    test_InvalidateLayout();
    test_line_spacing();
    test_GetOverhangMetrics();
    test_tab_stops();

    IDWriteFactory_Release(factory);
}
